zefyr

theme.dart 18KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580
  1. import 'package:flutter/material.dart';
  2. /// Applies a Zefyr editor theme to descendant widgets.
  3. ///
  4. /// Describes colors and typographic styles.
  5. ///
  6. /// Descendant widgets obtain the current theme's [ZefyrThemeData] object using
  7. /// [ZefyrTheme.of].
  8. class ZefyrTheme extends InheritedWidget {
  9. final ZefyrThemeData data;
  10. /// Applies the given theme [data] to [child].
  11. ///
  12. /// The [data] and [child] arguments must not be null.
  13. ZefyrTheme({
  14. Key key,
  15. @required this.data,
  16. @required Widget child,
  17. }) : assert(data != null),
  18. assert(child != null),
  19. super(key: key, child: child);
  20. @override
  21. bool updateShouldNotify(ZefyrTheme oldWidget) {
  22. return data != oldWidget.data;
  23. }
  24. /// The data from the closest [ZefyrTheme] instance that encloses the given
  25. /// context.
  26. ///
  27. /// Returns `null` if there is no [ZefyrTheme] in the given build context
  28. /// and [nullOk] is set to `true`. If [nullOk] is set to `false` (default)
  29. /// then this method asserts.
  30. static ZefyrThemeData of(BuildContext context, {bool nullOk = false}) {
  31. final ZefyrTheme widget =
  32. context.dependOnInheritedWidgetOfExactType<ZefyrTheme>();
  33. if (widget == null && nullOk) return null;
  34. assert(widget != null,
  35. '$ZefyrTheme.of() called with a context that does not contain a ZefyrEditor.');
  36. return widget.data;
  37. }
  38. }
  39. /// Holds colors and typography values for a Zefyr design theme.
  40. ///
  41. /// To obtain the current theme, use [ZefyrTheme.of].
  42. @immutable
  43. class ZefyrThemeData {
  44. /// Default theme used for document lines in Zefyr editor.
  45. ///
  46. /// Defines text style and spacing for regular paragraphs of text with
  47. /// no style attributes applied.
  48. final LineTheme defaultLineTheme;
  49. /// The text styles, padding and decorations used to render text with
  50. /// different style attributes.
  51. final AttributeTheme attributeTheme;
  52. /// The width of indentation used for blocks (lists, quotes, code).
  53. final double indentWidth;
  54. /// The colors used to render editor toolbar.
  55. final ToolbarTheme toolbarTheme;
  56. /// Creates a [ZefyrThemeData] given a set of exact values.
  57. const ZefyrThemeData({
  58. this.defaultLineTheme,
  59. this.attributeTheme,
  60. this.indentWidth,
  61. this.toolbarTheme,
  62. });
  63. /// The default editor theme.
  64. factory ZefyrThemeData.fallback(BuildContext context) {
  65. final defaultStyle = DefaultTextStyle.of(context);
  66. final defaultLineTheme = LineTheme(
  67. textStyle: defaultStyle.style.copyWith(
  68. fontSize: 16.0,
  69. height: 1.3,
  70. ),
  71. padding: EdgeInsets.symmetric(vertical: 8.0),
  72. );
  73. return ZefyrThemeData(
  74. defaultLineTheme: defaultLineTheme,
  75. attributeTheme: AttributeTheme.fallback(context, defaultLineTheme),
  76. indentWidth: 16.0,
  77. toolbarTheme: ToolbarTheme.fallback(context),
  78. );
  79. }
  80. /// Creates a copy of this theme but with the given fields replaced with
  81. /// the new values.
  82. ZefyrThemeData copyWith({
  83. LineTheme defaultLineTheme,
  84. AttributeTheme attributeTheme,
  85. double indentWidth,
  86. ToolbarTheme toolbarTheme,
  87. }) {
  88. return ZefyrThemeData(
  89. defaultLineTheme: defaultLineTheme ?? this.defaultLineTheme,
  90. attributeTheme: attributeTheme ?? this.attributeTheme,
  91. indentWidth: indentWidth ?? this.indentWidth,
  92. toolbarTheme: toolbarTheme ?? this.toolbarTheme,
  93. );
  94. }
  95. /// Creates a new [ZefyrThemeData] where each property from this object has
  96. /// been merged with the matching text style from the `other` object.
  97. ZefyrThemeData merge(ZefyrThemeData other) {
  98. if (other == null) return this;
  99. return copyWith(
  100. defaultLineTheme: defaultLineTheme?.merge(other.defaultLineTheme) ??
  101. other.defaultLineTheme,
  102. attributeTheme:
  103. attributeTheme?.merge(other.attributeTheme) ?? other.attributeTheme,
  104. indentWidth: other.indentWidth ?? indentWidth,
  105. toolbarTheme:
  106. toolbarTheme?.merge(other.toolbarTheme) ?? other.toolbarTheme,
  107. );
  108. }
  109. @override
  110. bool operator ==(other) {
  111. if (other.runtimeType != runtimeType) return false;
  112. final ZefyrThemeData otherData = other;
  113. return (otherData.defaultLineTheme == defaultLineTheme) &&
  114. (otherData.attributeTheme == attributeTheme) &&
  115. (otherData.indentWidth == indentWidth) &&
  116. (otherData.toolbarTheme == toolbarTheme);
  117. }
  118. @override
  119. int get hashCode {
  120. return hashList([
  121. defaultLineTheme,
  122. attributeTheme,
  123. indentWidth,
  124. toolbarTheme,
  125. ]);
  126. }
  127. }
  128. /// Holds typography values for a document line in Zefyr editor.
  129. ///
  130. /// Applicable for regular paragraphs, headings and lines within blocks
  131. /// (lists, quotes). Blocks may override some of these values using [BlockTheme].
  132. @immutable
  133. class LineTheme {
  134. /// Default text style for a document line.
  135. final TextStyle textStyle;
  136. /// Additional space around a document line.
  137. final EdgeInsets padding;
  138. /// Creates a [LineTheme] given a set of exact values.
  139. LineTheme({this.textStyle, this.padding})
  140. : assert(textStyle != null),
  141. assert(padding != null);
  142. /// Creates a copy of this theme but with the given fields replaced with
  143. /// the new values.
  144. LineTheme copyWith({TextStyle textStyle, EdgeInsets padding}) {
  145. return LineTheme(
  146. textStyle: textStyle ?? this.textStyle,
  147. padding: padding ?? this.padding,
  148. );
  149. }
  150. /// Creates a new [LineTheme] where each property from this object has
  151. /// been merged with the matching property from the `other` object.
  152. ///
  153. /// Text style is merged using [TextStyle.merge] when this and other
  154. /// theme have this value set.
  155. ///
  156. /// If padding property is set in other then it replaces value of this
  157. /// theme.
  158. LineTheme merge(LineTheme other) {
  159. if (other == null) return this;
  160. return copyWith(
  161. textStyle: textStyle?.merge(other.textStyle) ?? other.textStyle,
  162. padding: other.padding ?? padding,
  163. );
  164. }
  165. @override
  166. bool operator ==(other) {
  167. if (other.runtimeType != runtimeType) return false;
  168. final LineTheme otherTheme = other;
  169. return (otherTheme.textStyle == textStyle) &&
  170. (otherTheme.padding == padding);
  171. }
  172. @override
  173. int get hashCode => hashValues(textStyle, padding);
  174. }
  175. /// Holds typography values for a block of lines in Zefyr editor.
  176. @immutable
  177. class BlockTheme {
  178. /// Default text style for all text within a block, can be null.
  179. ///
  180. /// Takes precedence over line-level text style set by [LineTheme] if
  181. /// [inheritLineTextStyle] is set to `false`. Otherwise this text style
  182. /// is merged with the line's style.
  183. final TextStyle textStyle;
  184. /// Whether [textStyle] specified by this block theme should be merged with
  185. /// text style of each individual line.
  186. ///
  187. /// Only applicable if [textStyle] is not null.
  188. ///
  189. /// If set to `true` then [textStyle] is merged with text style specified
  190. /// by [LineTheme] of each line within a block. Otherwise [textStyle]
  191. /// takes precedence and replaces style of [LineTheme].
  192. final bool inheritLineTextStyle;
  193. /// Space around the block.
  194. final EdgeInsets padding;
  195. /// Space around each individual line within a block, can be null.
  196. ///
  197. /// Takes precedence over line padding set in [LineTheme].
  198. final EdgeInsets linePadding;
  199. /// Creates a [BlockTheme] given a set of exact values.
  200. const BlockTheme({
  201. this.textStyle,
  202. this.inheritLineTextStyle = true,
  203. this.padding,
  204. this.linePadding,
  205. });
  206. /// Creates a copy of this theme but with the given fields replaced with
  207. /// the new values.
  208. BlockTheme copyWith({
  209. TextStyle textStyle,
  210. EdgeInsets padding,
  211. bool inheritLineTextStyle,
  212. EdgeInsets linePadding,
  213. }) {
  214. return BlockTheme(
  215. textStyle: textStyle ?? this.textStyle,
  216. inheritLineTextStyle: inheritLineTextStyle ?? this.inheritLineTextStyle,
  217. padding: padding ?? this.padding,
  218. linePadding: linePadding ?? this.linePadding,
  219. );
  220. }
  221. /// Creates a new [BlockTheme] where each property from this object has
  222. /// been merged with the matching property from the `other` object.
  223. ///
  224. /// Text style is merged using [TextStyle.merge] when this and other
  225. /// theme have this field set.
  226. ///
  227. /// If padding property is set in other then it replaces value of this
  228. /// theme. [linePadding] follows the same logic.
  229. BlockTheme merge(BlockTheme other) {
  230. if (other == null) return this;
  231. return copyWith(
  232. textStyle: textStyle?.merge(other.textStyle) ?? other.textStyle,
  233. inheritLineTextStyle: other.inheritLineTextStyle ?? inheritLineTextStyle,
  234. padding: other.padding ?? padding,
  235. linePadding: other.linePadding ?? linePadding,
  236. );
  237. }
  238. @override
  239. bool operator ==(other) {
  240. if (other.runtimeType != runtimeType) return false;
  241. final BlockTheme otherTheme = other;
  242. return (otherTheme.textStyle == textStyle) &&
  243. (otherTheme.inheritLineTextStyle == inheritLineTextStyle) &&
  244. (otherTheme.padding == padding) &&
  245. (otherTheme.linePadding == linePadding);
  246. }
  247. @override
  248. int get hashCode =>
  249. hashValues(textStyle, inheritLineTextStyle, padding, linePadding);
  250. }
  251. /// Holds style information for all format attributes supported by Zefyr editor.
  252. @immutable
  253. class AttributeTheme {
  254. /// Style used to render "bold" text.
  255. final TextStyle bold;
  256. /// Style used to render "italic" text.
  257. final TextStyle italic;
  258. /// Style used to render text containing links.
  259. final TextStyle link;
  260. /// Style theme used to render largest headings.
  261. final LineTheme heading1;
  262. /// Style theme used to render medium headings.
  263. final LineTheme heading2;
  264. /// Style theme used to render smaller headings.
  265. final LineTheme heading3;
  266. /// Style theme used to render bullet lists.
  267. final BlockTheme bulletList;
  268. /// Style theme used to render number lists.
  269. final BlockTheme numberList;
  270. /// Style theme used to render quote blocks.
  271. final BlockTheme quote;
  272. /// Style theme used to render code blocks.
  273. final BlockTheme code;
  274. /// Creates a [AttributeTheme] given a set of exact values.
  275. AttributeTheme({
  276. this.bold,
  277. this.italic,
  278. this.link,
  279. this.heading1,
  280. this.heading2,
  281. this.heading3,
  282. this.bulletList,
  283. this.numberList,
  284. this.quote,
  285. this.code,
  286. });
  287. /// The default attribute theme.
  288. factory AttributeTheme.fallback(
  289. BuildContext context, LineTheme defaultLineTheme) {
  290. final theme = Theme.of(context);
  291. String monospaceFontFamily;
  292. switch (theme.platform) {
  293. case TargetPlatform.iOS:
  294. monospaceFontFamily = 'Menlo';
  295. break;
  296. case TargetPlatform.android:
  297. case TargetPlatform.fuchsia:
  298. monospaceFontFamily = 'Roboto Mono';
  299. break;
  300. default:
  301. throw UnimplementedError("Platform ${theme.platform} not implemented.");
  302. }
  303. return AttributeTheme(
  304. bold: TextStyle(fontWeight: FontWeight.bold),
  305. italic: TextStyle(fontStyle: FontStyle.italic),
  306. link: TextStyle(
  307. decoration: TextDecoration.underline,
  308. color: theme.accentColor,
  309. ),
  310. heading1: LineTheme(
  311. textStyle: defaultLineTheme.textStyle.copyWith(
  312. fontSize: 34.0,
  313. color: defaultLineTheme.textStyle.color.withOpacity(0.7),
  314. height: 1.15,
  315. fontWeight: FontWeight.w300,
  316. ),
  317. padding: EdgeInsets.only(top: 16.0),
  318. ),
  319. heading2: LineTheme(
  320. textStyle: defaultLineTheme.textStyle.copyWith(
  321. fontSize: 24.0,
  322. color: defaultLineTheme.textStyle.color.withOpacity(0.7),
  323. height: 1.15,
  324. fontWeight: FontWeight.normal,
  325. ),
  326. padding: EdgeInsets.only(top: 8.0),
  327. ),
  328. heading3: LineTheme(
  329. textStyle: defaultLineTheme.textStyle.copyWith(
  330. fontSize: 20.0,
  331. color: defaultLineTheme.textStyle.color.withOpacity(0.7),
  332. height: 1.15,
  333. fontWeight: FontWeight.w500,
  334. ),
  335. padding: EdgeInsets.only(top: 8.0),
  336. ),
  337. bulletList: BlockTheme(
  338. padding: EdgeInsets.symmetric(vertical: 8.0),
  339. linePadding: EdgeInsets.symmetric(vertical: 2.0),
  340. ),
  341. numberList: BlockTheme(
  342. padding: EdgeInsets.symmetric(vertical: 8.0),
  343. linePadding: EdgeInsets.symmetric(vertical: 2.0),
  344. ),
  345. quote: BlockTheme(
  346. padding: EdgeInsets.symmetric(vertical: 8.0),
  347. textStyle: TextStyle(
  348. color: defaultLineTheme.textStyle.color.withOpacity(0.6),
  349. ),
  350. inheritLineTextStyle: true,
  351. ),
  352. code: BlockTheme(
  353. padding: EdgeInsets.symmetric(vertical: 8.0),
  354. textStyle: TextStyle(
  355. fontFamily: monospaceFontFamily,
  356. fontSize: 14.0,
  357. color: defaultLineTheme.textStyle.color.withOpacity(0.8),
  358. height: 1.25,
  359. ),
  360. inheritLineTextStyle: false,
  361. linePadding: EdgeInsets.zero,
  362. ),
  363. );
  364. }
  365. /// Creates a new [AttributeTheme] where each property from this object has
  366. /// been merged with the matching property from the `other` object.
  367. AttributeTheme copyWith({
  368. TextStyle bold,
  369. TextStyle italic,
  370. TextStyle link,
  371. LineTheme heading1,
  372. LineTheme heading2,
  373. LineTheme heading3,
  374. BlockTheme bulletList,
  375. BlockTheme numberList,
  376. BlockTheme quote,
  377. BlockTheme code,
  378. }) {
  379. return AttributeTheme(
  380. bold: bold ?? this.bold,
  381. italic: italic ?? this.italic,
  382. link: link ?? this.link,
  383. heading1: heading1 ?? this.heading1,
  384. heading2: heading2 ?? this.heading2,
  385. heading3: heading3 ?? this.heading3,
  386. bulletList: bulletList ?? this.bulletList,
  387. numberList: numberList ?? this.numberList,
  388. quote: quote ?? this.quote,
  389. code: code ?? this.code,
  390. );
  391. }
  392. /// Creates a new [AttributeTheme] where each property from this object has
  393. /// been merged with the matching property from the `other` object.
  394. AttributeTheme merge(AttributeTheme other) {
  395. if (other == null) return this;
  396. return copyWith(
  397. bold: bold?.merge(other.bold) ?? other.bold,
  398. italic: italic?.merge(other.italic) ?? other.italic,
  399. link: link?.merge(other.link) ?? other.link,
  400. heading1: heading1?.merge(other.heading1) ?? other.heading1,
  401. heading2: heading2?.merge(other.heading2) ?? other.heading2,
  402. heading3: heading3?.merge(other.heading3) ?? other.heading3,
  403. bulletList: bulletList?.merge(other.bulletList) ?? other.bulletList,
  404. numberList: numberList?.merge(other.numberList) ?? other.numberList,
  405. quote: quote?.merge(other.quote) ?? other.quote,
  406. code: code?.merge(other.code) ?? other.code,
  407. );
  408. }
  409. @override
  410. bool operator ==(other) {
  411. if (other.runtimeType != runtimeType) return false;
  412. final AttributeTheme otherTheme = other;
  413. return (otherTheme.bold == bold) &&
  414. (otherTheme.italic == italic) &&
  415. (otherTheme.link == link) &&
  416. (otherTheme.heading1 == heading1) &&
  417. (otherTheme.heading2 == heading2) &&
  418. (otherTheme.heading3 == heading3) &&
  419. (otherTheme.bulletList == bulletList) &&
  420. (otherTheme.numberList == numberList) &&
  421. (otherTheme.quote == quote) &&
  422. (otherTheme.code == code);
  423. }
  424. @override
  425. int get hashCode {
  426. return hashList([
  427. bold,
  428. italic,
  429. link,
  430. heading1,
  431. heading2,
  432. heading3,
  433. bulletList,
  434. numberList,
  435. quote,
  436. code,
  437. ]);
  438. }
  439. }
  440. /// Defines styles and colors for Zefyr editor toolbar.
  441. class ToolbarTheme {
  442. /// The background color of the toolbar.
  443. final Color color;
  444. /// Color of buttons in toggled state.
  445. final Color toggleColor;
  446. /// Color of button icons.
  447. final Color iconFillColor;
  448. /// Color of button icons.
  449. final Color iconColor;
  450. /// Color of button icons in disabled state.
  451. final Color disabledIconColor;
  452. final Color dividerColor;
  453. /// Creates default theme for editor toolbar.
  454. factory ToolbarTheme.fallback(BuildContext context) {
  455. final theme = Theme.of(context);
  456. return ToolbarTheme._(
  457. color: theme.brightness == Brightness.dark ? Color(0xFF282828) : Color(0xFFFFFFFF),
  458. toggleColor: Color(0xFF01AAFF),
  459. iconFillColor: theme.brightness == Brightness.dark ? Color(0xFF1F1F1F) : Color(0xFFF2F2F2),
  460. iconColor: Color(0xFF595959),
  461. disabledIconColor: Color(0xFF8C8C8C),
  462. dividerColor: theme.brightness == Brightness.dark ? Color(0xFF3B3B3B) : Color(0xFFF2F2F2),
  463. );
  464. }
  465. ToolbarTheme._({
  466. @required this.color,
  467. @required this.toggleColor,
  468. @required this.iconFillColor,
  469. @required this.iconColor,
  470. @required this.disabledIconColor,
  471. @required this.dividerColor,
  472. });
  473. /// Creates a new [ToolbarTheme] where each property from this object has
  474. /// been merged with the matching property from the `other` object.
  475. ToolbarTheme copyWith({
  476. Color color,
  477. Color toggleColor,
  478. Color iconFillColor,
  479. Color iconColor,
  480. Color disabledIconColor,
  481. Color dividerColor,
  482. }) {
  483. return ToolbarTheme._(
  484. color: color ?? this.color,
  485. toggleColor: toggleColor ?? this.toggleColor,
  486. iconFillColor: iconFillColor ?? this.iconFillColor,
  487. iconColor: iconColor ?? this.iconColor,
  488. disabledIconColor: disabledIconColor ?? this.disabledIconColor,
  489. dividerColor: dividerColor ?? this.dividerColor,
  490. );
  491. }
  492. /// Creates a new [ToolbarTheme] where each property from this object has
  493. /// been merged with the matching property from the `other` object.
  494. ToolbarTheme merge(ToolbarTheme other) {
  495. if (other == null) return this;
  496. return copyWith(
  497. color: other.color ?? color,
  498. toggleColor: other.toggleColor ?? toggleColor,
  499. iconFillColor: other.iconFillColor ?? iconFillColor,
  500. iconColor: other.iconColor ?? iconColor,
  501. disabledIconColor: other.disabledIconColor ?? disabledIconColor,
  502. dividerColor: other.dividerColor ?? dividerColor,
  503. );
  504. }
  505. @override
  506. bool operator ==(other) {
  507. if (other.runtimeType != runtimeType) return false;
  508. final ToolbarTheme otherTheme = other;
  509. return (otherTheme.color == color) &&
  510. (otherTheme.toggleColor == toggleColor) &&
  511. (otherTheme.iconFillColor == iconFillColor) &&
  512. (otherTheme.iconColor == iconColor) &&
  513. (otherTheme.disabledIconColor == disabledIconColor);
  514. (otherTheme.dividerColor == dividerColor);
  515. }
  516. @override
  517. int get hashCode =>
  518. hashValues(color, toggleColor, iconFillColor, iconColor, disabledIconColor, dividerColor);
  519. }