set.go 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634
  1. package lipgloss
  2. // This could (should) probably just be moved into NewStyle(). We've broken it
  3. // out, so we can call it in a lazy way.
  4. func (s *Style) init() {
  5. if s.rules == nil {
  6. s.rules = make(rules)
  7. }
  8. }
  9. // Set a value on the underlying rules map.
  10. func (s *Style) set(key propKey, value interface{}) {
  11. s.init()
  12. switch v := value.(type) {
  13. case int:
  14. // We don't allow negative integers on any of our values, so just keep
  15. // them at zero or above. We could use uints instead, but the
  16. // conversions are a little tedious, so we're sticking with ints for
  17. // sake of usability.
  18. s.rules[key] = max(0, v)
  19. default:
  20. s.rules[key] = v
  21. }
  22. }
  23. // Bold sets a bold formatting rule.
  24. func (s Style) Bold(v bool) Style {
  25. s.set(boldKey, v)
  26. return s
  27. }
  28. // Italic sets an italic formatting rule. In some terminal emulators this will
  29. // render with "reverse" coloring if not italic font variant is available.
  30. func (s Style) Italic(v bool) Style {
  31. s.set(italicKey, v)
  32. return s
  33. }
  34. // Underline sets an underline rule. By default, underlines will not be drawn on
  35. // whitespace like margins and padding. To change this behavior set
  36. // UnderlineSpaces.
  37. func (s Style) Underline(v bool) Style {
  38. s.set(underlineKey, v)
  39. return s
  40. }
  41. // Strikethrough sets a strikethrough rule. By default, strikes will not be
  42. // drawn on whitespace like margins and padding. To change this behavior set
  43. // StrikethroughSpaces.
  44. func (s Style) Strikethrough(v bool) Style {
  45. s.set(strikethroughKey, v)
  46. return s
  47. }
  48. // Reverse sets a rule for inverting foreground and background colors.
  49. func (s Style) Reverse(v bool) Style {
  50. s.set(reverseKey, v)
  51. return s
  52. }
  53. // Blink sets a rule for blinking foreground text.
  54. func (s Style) Blink(v bool) Style {
  55. s.set(blinkKey, v)
  56. return s
  57. }
  58. // Faint sets a rule for rendering the foreground color in a dimmer shade.
  59. func (s Style) Faint(v bool) Style {
  60. s.set(faintKey, v)
  61. return s
  62. }
  63. // Foreground sets a foreground color.
  64. //
  65. // // Sets the foreground to blue
  66. // s := lipgloss.NewStyle().Foreground(lipgloss.Color("#0000ff"))
  67. //
  68. // // Removes the foreground color
  69. // s.Foreground(lipgloss.NoColor)
  70. func (s Style) Foreground(c TerminalColor) Style {
  71. s.set(foregroundKey, c)
  72. return s
  73. }
  74. // Background sets a background color.
  75. func (s Style) Background(c TerminalColor) Style {
  76. s.set(backgroundKey, c)
  77. return s
  78. }
  79. // Width sets the width of the block before applying margins. The width, if
  80. // set, also determines where text will wrap.
  81. func (s Style) Width(i int) Style {
  82. s.set(widthKey, i)
  83. return s
  84. }
  85. // Height sets the height of the block before applying margins. If the height of
  86. // the text block is less than this value after applying padding (or not), the
  87. // block will be set to this height.
  88. func (s Style) Height(i int) Style {
  89. s.set(heightKey, i)
  90. return s
  91. }
  92. // Align is a shorthand method for setting horizontal and vertical alignment.
  93. //
  94. // With one argument, the position value is applied to the horizontal alignment.
  95. //
  96. // With two arguments, the value is applied to the vertical and horizontal
  97. // alignments, in that order.
  98. func (s Style) Align(p ...Position) Style {
  99. if len(p) > 0 {
  100. s.set(alignHorizontalKey, p[0])
  101. }
  102. if len(p) > 1 {
  103. s.set(alignVerticalKey, p[1])
  104. }
  105. return s
  106. }
  107. // AlignHorizontal sets a horizontal text alignment rule.
  108. func (s Style) AlignHorizontal(p Position) Style {
  109. s.set(alignHorizontalKey, p)
  110. return s
  111. }
  112. // AlignVertical sets a text alignment rule.
  113. func (s Style) AlignVertical(p Position) Style {
  114. s.set(alignVerticalKey, p)
  115. return s
  116. }
  117. // Padding is a shorthand method for setting padding on all sides at once.
  118. //
  119. // With one argument, the value is applied to all sides.
  120. //
  121. // With two arguments, the value is applied to the vertical and horizontal
  122. // sides, in that order.
  123. //
  124. // With three arguments, the value is applied to the top side, the horizontal
  125. // sides, and the bottom side, in that order.
  126. //
  127. // With four arguments, the value is applied clockwise starting from the top
  128. // side, followed by the right side, then the bottom, and finally the left.
  129. //
  130. // With more than four arguments no padding will be added.
  131. func (s Style) Padding(i ...int) Style {
  132. top, right, bottom, left, ok := whichSidesInt(i...)
  133. if !ok {
  134. return s
  135. }
  136. s.set(paddingTopKey, top)
  137. s.set(paddingRightKey, right)
  138. s.set(paddingBottomKey, bottom)
  139. s.set(paddingLeftKey, left)
  140. return s
  141. }
  142. // PaddingLeft adds padding on the left.
  143. func (s Style) PaddingLeft(i int) Style {
  144. s.set(paddingLeftKey, i)
  145. return s
  146. }
  147. // PaddingRight adds padding on the right.
  148. func (s Style) PaddingRight(i int) Style {
  149. s.set(paddingRightKey, i)
  150. return s
  151. }
  152. // PaddingTop adds padding to the top of the block.
  153. func (s Style) PaddingTop(i int) Style {
  154. s.set(paddingTopKey, i)
  155. return s
  156. }
  157. // PaddingBottom adds padding to the bottom of the block.
  158. func (s Style) PaddingBottom(i int) Style {
  159. s.set(paddingBottomKey, i)
  160. return s
  161. }
  162. // ColorWhitespace determines whether or not the background color should be
  163. // applied to the padding. This is true by default as it's more than likely the
  164. // desired and expected behavior, but it can be disabled for certain graphic
  165. // effects.
  166. func (s Style) ColorWhitespace(v bool) Style {
  167. s.set(colorWhitespaceKey, v)
  168. return s
  169. }
  170. // Margin is a shorthand method for setting margins on all sides at once.
  171. //
  172. // With one argument, the value is applied to all sides.
  173. //
  174. // With two arguments, the value is applied to the vertical and horizontal
  175. // sides, in that order.
  176. //
  177. // With three arguments, the value is applied to the top side, the horizontal
  178. // sides, and the bottom side, in that order.
  179. //
  180. // With four arguments, the value is applied clockwise starting from the top
  181. // side, followed by the right side, then the bottom, and finally the left.
  182. //
  183. // With more than four arguments no margin will be added.
  184. func (s Style) Margin(i ...int) Style {
  185. top, right, bottom, left, ok := whichSidesInt(i...)
  186. if !ok {
  187. return s
  188. }
  189. s.set(marginTopKey, top)
  190. s.set(marginRightKey, right)
  191. s.set(marginBottomKey, bottom)
  192. s.set(marginLeftKey, left)
  193. return s
  194. }
  195. // MarginLeft sets the value of the left margin.
  196. func (s Style) MarginLeft(i int) Style {
  197. s.set(marginLeftKey, i)
  198. return s
  199. }
  200. // MarginRight sets the value of the right margin.
  201. func (s Style) MarginRight(i int) Style {
  202. s.set(marginRightKey, i)
  203. return s
  204. }
  205. // MarginTop sets the value of the top margin.
  206. func (s Style) MarginTop(i int) Style {
  207. s.set(marginTopKey, i)
  208. return s
  209. }
  210. // MarginBottom sets the value of the bottom margin.
  211. func (s Style) MarginBottom(i int) Style {
  212. s.set(marginBottomKey, i)
  213. return s
  214. }
  215. // MarginBackground sets the background color of the margin. Note that this is
  216. // also set when inheriting from a style with a background color. In that case
  217. // the background color on that style will set the margin color on this style.
  218. func (s Style) MarginBackground(c TerminalColor) Style {
  219. s.set(marginBackgroundKey, c)
  220. return s
  221. }
  222. // Border is shorthand for setting the border style and which sides should
  223. // have a border at once. The variadic argument sides works as follows:
  224. //
  225. // With one value, the value is applied to all sides.
  226. //
  227. // With two values, the values are applied to the vertical and horizontal
  228. // sides, in that order.
  229. //
  230. // With three values, the values are applied to the top side, the horizontal
  231. // sides, and the bottom side, in that order.
  232. //
  233. // With four values, the values are applied clockwise starting from the top
  234. // side, followed by the right side, then the bottom, and finally the left.
  235. //
  236. // With more than four arguments the border will be applied to all sides.
  237. //
  238. // Examples:
  239. //
  240. // // Applies borders to the top and bottom only
  241. // lipgloss.NewStyle().Border(lipgloss.NormalBorder(), true, false)
  242. //
  243. // // Applies rounded borders to the right and bottom only
  244. // lipgloss.NewStyle().Border(lipgloss.RoundedBorder(), false, true, true, false)
  245. func (s Style) Border(b Border, sides ...bool) Style {
  246. s.set(borderStyleKey, b)
  247. top, right, bottom, left, ok := whichSidesBool(sides...)
  248. if !ok {
  249. top = true
  250. right = true
  251. bottom = true
  252. left = true
  253. }
  254. s.set(borderTopKey, top)
  255. s.set(borderRightKey, right)
  256. s.set(borderBottomKey, bottom)
  257. s.set(borderLeftKey, left)
  258. return s
  259. }
  260. // BorderStyle defines the Border on a style. A Border contains a series of
  261. // definitions for the sides and corners of a border.
  262. //
  263. // Note that if border visibility has not been set for any sides when setting
  264. // the border style, the border will be enabled for all sides during rendering.
  265. //
  266. // You can define border characters as you'd like, though several default
  267. // styles are included: NormalBorder(), RoundedBorder(), BlockBorder(),
  268. // OuterHalfBlockBorder(), InnerHalfBlockBorder(), ThickBorder(),
  269. // and DoubleBorder().
  270. //
  271. // Example:
  272. //
  273. // lipgloss.NewStyle().BorderStyle(lipgloss.ThickBorder())
  274. func (s Style) BorderStyle(b Border) Style {
  275. s.set(borderStyleKey, b)
  276. return s
  277. }
  278. // BorderTop determines whether or not to draw a top border.
  279. func (s Style) BorderTop(v bool) Style {
  280. s.set(borderTopKey, v)
  281. return s
  282. }
  283. // BorderRight determines whether or not to draw a right border.
  284. func (s Style) BorderRight(v bool) Style {
  285. s.set(borderRightKey, v)
  286. return s
  287. }
  288. // BorderBottom determines whether or not to draw a bottom border.
  289. func (s Style) BorderBottom(v bool) Style {
  290. s.set(borderBottomKey, v)
  291. return s
  292. }
  293. // BorderLeft determines whether or not to draw a left border.
  294. func (s Style) BorderLeft(v bool) Style {
  295. s.set(borderLeftKey, v)
  296. return s
  297. }
  298. // BorderForeground is a shorthand function for setting all of the
  299. // foreground colors of the borders at once. The arguments work as follows:
  300. //
  301. // With one argument, the argument is applied to all sides.
  302. //
  303. // With two arguments, the arguments are applied to the vertical and horizontal
  304. // sides, in that order.
  305. //
  306. // With three arguments, the arguments are applied to the top side, the
  307. // horizontal sides, and the bottom side, in that order.
  308. //
  309. // With four arguments, the arguments are applied clockwise starting from the
  310. // top side, followed by the right side, then the bottom, and finally the left.
  311. //
  312. // With more than four arguments nothing will be set.
  313. func (s Style) BorderForeground(c ...TerminalColor) Style {
  314. if len(c) == 0 {
  315. return s
  316. }
  317. top, right, bottom, left, ok := whichSidesColor(c...)
  318. if !ok {
  319. return s
  320. }
  321. s.set(borderTopForegroundKey, top)
  322. s.set(borderRightForegroundKey, right)
  323. s.set(borderBottomForegroundKey, bottom)
  324. s.set(borderLeftForegroundKey, left)
  325. return s
  326. }
  327. // BorderTopForeground set the foreground color for the top of the border.
  328. func (s Style) BorderTopForeground(c TerminalColor) Style {
  329. s.set(borderTopForegroundKey, c)
  330. return s
  331. }
  332. // BorderRightForeground sets the foreground color for the right side of the
  333. // border.
  334. func (s Style) BorderRightForeground(c TerminalColor) Style {
  335. s.set(borderRightForegroundKey, c)
  336. return s
  337. }
  338. // BorderBottomForeground sets the foreground color for the bottom of the
  339. // border.
  340. func (s Style) BorderBottomForeground(c TerminalColor) Style {
  341. s.set(borderBottomForegroundKey, c)
  342. return s
  343. }
  344. // BorderLeftForeground sets the foreground color for the left side of the
  345. // border.
  346. func (s Style) BorderLeftForeground(c TerminalColor) Style {
  347. s.set(borderLeftForegroundKey, c)
  348. return s
  349. }
  350. // BorderBackground is a shorthand function for setting all of the
  351. // background colors of the borders at once. The arguments work as follows:
  352. //
  353. // With one argument, the argument is applied to all sides.
  354. //
  355. // With two arguments, the arguments are applied to the vertical and horizontal
  356. // sides, in that order.
  357. //
  358. // With three arguments, the arguments are applied to the top side, the
  359. // horizontal sides, and the bottom side, in that order.
  360. //
  361. // With four arguments, the arguments are applied clockwise starting from the
  362. // top side, followed by the right side, then the bottom, and finally the left.
  363. //
  364. // With more than four arguments nothing will be set.
  365. func (s Style) BorderBackground(c ...TerminalColor) Style {
  366. if len(c) == 0 {
  367. return s
  368. }
  369. top, right, bottom, left, ok := whichSidesColor(c...)
  370. if !ok {
  371. return s
  372. }
  373. s.set(borderTopBackgroundKey, top)
  374. s.set(borderRightBackgroundKey, right)
  375. s.set(borderBottomBackgroundKey, bottom)
  376. s.set(borderLeftBackgroundKey, left)
  377. return s
  378. }
  379. // BorderTopBackground sets the background color of the top of the border.
  380. func (s Style) BorderTopBackground(c TerminalColor) Style {
  381. s.set(borderTopBackgroundKey, c)
  382. return s
  383. }
  384. // BorderRightBackground sets the background color of right side the border.
  385. func (s Style) BorderRightBackground(c TerminalColor) Style {
  386. s.set(borderRightBackgroundKey, c)
  387. return s
  388. }
  389. // BorderBottomBackground sets the background color of the bottom of the
  390. // border.
  391. func (s Style) BorderBottomBackground(c TerminalColor) Style {
  392. s.set(borderBottomBackgroundKey, c)
  393. return s
  394. }
  395. // BorderLeftBackground set the background color of the left side of the
  396. // border.
  397. func (s Style) BorderLeftBackground(c TerminalColor) Style {
  398. s.set(borderLeftBackgroundKey, c)
  399. return s
  400. }
  401. // Inline makes rendering output one line and disables the rendering of
  402. // margins, padding and borders. This is useful when you need a style to apply
  403. // only to font rendering and don't want it to change any physical dimensions.
  404. // It works well with Style.MaxWidth.
  405. //
  406. // Because this in intended to be used at the time of render, this method will
  407. // not mutate the style and instead return a copy.
  408. //
  409. // Example:
  410. //
  411. // var userInput string = "..."
  412. // var userStyle = text.Style{ /* ... */ }
  413. // fmt.Println(userStyle.Inline(true).Render(userInput))
  414. func (s Style) Inline(v bool) Style {
  415. o := s.Copy()
  416. o.set(inlineKey, v)
  417. return o
  418. }
  419. // MaxWidth applies a max width to a given style. This is useful in enforcing
  420. // a certain width at render time, particularly with arbitrary strings and
  421. // styles.
  422. //
  423. // Because this in intended to be used at the time of render, this method will
  424. // not mutate the style and instead return a copy.
  425. //
  426. // Example:
  427. //
  428. // var userInput string = "..."
  429. // var userStyle = text.Style{ /* ... */ }
  430. // fmt.Println(userStyle.MaxWidth(16).Render(userInput))
  431. func (s Style) MaxWidth(n int) Style {
  432. o := s.Copy()
  433. o.set(maxWidthKey, n)
  434. return o
  435. }
  436. // MaxHeight applies a max height to a given style. This is useful in enforcing
  437. // a certain height at render time, particularly with arbitrary strings and
  438. // styles.
  439. //
  440. // Because this in intended to be used at the time of render, this method will
  441. // not mutate the style and instead return a copy.
  442. func (s Style) MaxHeight(n int) Style {
  443. o := s.Copy()
  444. o.set(maxHeightKey, n)
  445. return o
  446. }
  447. // UnderlineSpaces determines whether to underline spaces between words. By
  448. // default, this is true. Spaces can also be underlined without underlining the
  449. // text itself.
  450. func (s Style) UnderlineSpaces(v bool) Style {
  451. s.set(underlineSpacesKey, v)
  452. return s
  453. }
  454. // StrikethroughSpaces determines whether to apply strikethroughs to spaces
  455. // between words. By default, this is true. Spaces can also be struck without
  456. // underlining the text itself.
  457. func (s Style) StrikethroughSpaces(v bool) Style {
  458. s.set(strikethroughSpacesKey, v)
  459. return s
  460. }
  461. // Renderer sets the renderer for the style. This is useful for changing the
  462. // renderer for a style that is being used in a different context.
  463. func (s Style) Renderer(r *Renderer) Style {
  464. s.r = r
  465. return s
  466. }
  467. // whichSidesInt is a helper method for setting values on sides of a block based
  468. // on the number of arguments. It follows the CSS shorthand rules for blocks
  469. // like margin, padding. and borders. Here are how the rules work:
  470. //
  471. // 0 args: do nothing
  472. // 1 arg: all sides
  473. // 2 args: top -> bottom
  474. // 3 args: top -> horizontal -> bottom
  475. // 4 args: top -> right -> bottom -> left
  476. // 5+ args: do nothing.
  477. func whichSidesInt(i ...int) (top, right, bottom, left int, ok bool) {
  478. switch len(i) {
  479. case 1:
  480. top = i[0]
  481. bottom = i[0]
  482. left = i[0]
  483. right = i[0]
  484. ok = true
  485. case 2:
  486. top = i[0]
  487. bottom = i[0]
  488. left = i[1]
  489. right = i[1]
  490. ok = true
  491. case 3:
  492. top = i[0]
  493. left = i[1]
  494. right = i[1]
  495. bottom = i[2]
  496. ok = true
  497. case 4:
  498. top = i[0]
  499. right = i[1]
  500. bottom = i[2]
  501. left = i[3]
  502. ok = true
  503. }
  504. return top, right, bottom, left, ok
  505. }
  506. // whichSidesBool is like whichSidesInt, except it operates on a series of
  507. // boolean values. See the comment on whichSidesInt for details on how this
  508. // works.
  509. func whichSidesBool(i ...bool) (top, right, bottom, left bool, ok bool) {
  510. switch len(i) {
  511. case 1:
  512. top = i[0]
  513. bottom = i[0]
  514. left = i[0]
  515. right = i[0]
  516. ok = true
  517. case 2:
  518. top = i[0]
  519. bottom = i[0]
  520. left = i[1]
  521. right = i[1]
  522. ok = true
  523. case 3:
  524. top = i[0]
  525. left = i[1]
  526. right = i[1]
  527. bottom = i[2]
  528. ok = true
  529. case 4:
  530. top = i[0]
  531. right = i[1]
  532. bottom = i[2]
  533. left = i[3]
  534. ok = true
  535. }
  536. return top, right, bottom, left, ok
  537. }
  538. // whichSidesColor is like whichSides, except it operates on a series of
  539. // boolean values. See the comment on whichSidesInt for details on how this
  540. // works.
  541. func whichSidesColor(i ...TerminalColor) (top, right, bottom, left TerminalColor, ok bool) {
  542. switch len(i) {
  543. case 1:
  544. top = i[0]
  545. bottom = i[0]
  546. left = i[0]
  547. right = i[0]
  548. ok = true
  549. case 2:
  550. top = i[0]
  551. bottom = i[0]
  552. left = i[1]
  553. right = i[1]
  554. ok = true
  555. case 3:
  556. top = i[0]
  557. left = i[1]
  558. right = i[1]
  559. bottom = i[2]
  560. ok = true
  561. case 4:
  562. top = i[0]
  563. right = i[1]
  564. bottom = i[2]
  565. left = i[3]
  566. ok = true
  567. }
  568. return top, right, bottom, left, ok
  569. }