diff --git a/examples/Examples.re b/examples/Examples.re
index c376e47ab..ec801ceb2 100644
--- a/examples/Examples.re
+++ b/examples/Examples.re
@@ -150,6 +150,11 @@ let examples = [
render: _ => FileDragAndDrop.render(),
source: "FileDragAndDrop.re",
},
+ {
+ name: "Rich Text Example",
+ render: _ => RichTextExample.render(),
+ source: "RichTextExample.re",
+ },
{
name: "Shell: Open URL",
render: _ => URLFileOpen.render(),
diff --git a/examples/RichTextExample.re b/examples/RichTextExample.re
new file mode 100644
index 000000000..0e8cafd3e
--- /dev/null
+++ b/examples/RichTextExample.re
@@ -0,0 +1,40 @@
+open Revery;
+open Revery.UI;
+open Revery.UI.Components;
+open Revery.Font;
+
+let containerStyle =
+ Style.[
+ position(`Absolute),
+ top(0),
+ bottom(0),
+ left(0),
+ right(0),
+ alignItems(`Center),
+ justifyContent(`Center),
+ flexDirection(`Column),
+ ];
+
+module SampleRichText = {
+ let make = () => {
+ let richtext =
+ RichText.(
+ text("Hello ", ~color=Colors.red, ~fontWeight=Weight.Bold)
+ ++ text("world", ~color=Colors.green)
+ ++ text("!", ~color=Colors.yellow)
+ |> fontSize(20.)
+ |> italicized
+ );
+
+ let dimensions = RichText.measure(richtext);
+ let widthText = "Width: " ++ string_of_int(dimensions.width);
+ let heightText = "Height: " ++ string_of_int(dimensions.height);
+
+
+
+
+
+ ;
+ };
+};
+let render = () => ;
diff --git a/src/UI/Revery_UI.re b/src/UI/Revery_UI.re
index 56fd343dd..29a478d1a 100644
--- a/src/UI/Revery_UI.re
+++ b/src/UI/Revery_UI.re
@@ -8,6 +8,7 @@ module LayoutTypes = Layout.LayoutTypes;
module Style = Style;
module Transform = Transform;
module Selector = Selector;
+module RichText = RichText;
class node = class Node.node;
class viewNode = class ViewNode.viewNode;
diff --git a/src/UI/RichText.re b/src/UI/RichText.re
new file mode 100644
index 000000000..072dc9af8
--- /dev/null
+++ b/src/UI/RichText.re
@@ -0,0 +1,129 @@
+open Revery_Core;
+open Revery_Font;
+
+type textInfo = {
+ fontFamily: Family.t,
+ fontWeight: Weight.t,
+ italicized: bool,
+ monospaced: bool,
+ fontSize: float,
+ text: string,
+ color: Color.t,
+};
+type t =
+ | Leaf(textInfo)
+ | Node(t, t);
+
+let (++) = (left: t, right: t) => Node(left, right);
+
+let rec foldRight =
+ (fn: ('acc, textInfo) => 'acc, accumulator: 'acc, richtext: t) =>
+ switch (richtext) {
+ | Leaf(textInfo) => fn(accumulator, textInfo)
+ | Node(left, right) =>
+ let rightAcc = foldRight(fn, accumulator, right);
+ let leftAcc = foldRight(fn, rightAcc, left);
+ leftAcc;
+ };
+
+let rec map = (updateLeaf: textInfo => t, richtext: t) =>
+ switch (richtext) {
+ | Leaf(textInfo) => updateLeaf(textInfo)
+ | Node(left, right) =>
+ let newLeft = map(updateLeaf, left);
+ let newRight = map(updateLeaf, right);
+ Node(newLeft, newRight);
+ };
+
+let measure = (~smoothing=Smoothing.default, richtext: t) =>
+ foldRight(
+ (acc: Dimensions.t, textInfo) => {
+ let dimensions =
+ Revery_Draw.Text.measure(
+ ~smoothing,
+ ~fontFamily=
+ Family.toPath(
+ textInfo.fontFamily,
+ textInfo.fontWeight,
+ textInfo.italicized,
+ textInfo.monospaced,
+ ),
+ ~fontSize=textInfo.fontSize,
+ textInfo.text,
+ );
+ let width = acc.width + int_of_float(dimensions.width);
+ let height = max(acc.height, int_of_float(dimensions.height));
+
+ Dimensions.create(~top=0, ~left=0, ~width, ~height, ());
+ },
+ Dimensions.create(~top=0, ~left=0, ~width=0, ~height=0, ()),
+ richtext,
+ );
+
+module DSL = {
+ module Defaults = {
+ let fontFamily = Family.default;
+ let fontWeight = Weight.Normal;
+ let italicized = false;
+ let monospaced = false;
+ let fontSize = 14.;
+ let color = Colors.black;
+ };
+ let text =
+ (
+ ~fontFamily=Defaults.fontFamily,
+ ~fontWeight=Defaults.fontWeight,
+ ~italicized=Defaults.italicized,
+ ~monospaced=Defaults.monospaced,
+ ~fontSize=Defaults.fontSize,
+ ~color=Defaults.color,
+ text: string,
+ ) =>
+ Leaf({
+ fontFamily,
+ fontWeight,
+ italicized,
+ monospaced,
+ fontSize,
+ text,
+ color,
+ });
+
+ let fontWeight = (fontWeight: Weight.t, richtext: t) =>
+ richtext |> map(textInfo => Leaf({...textInfo, fontWeight}));
+ let thin = (richtext: t) =>
+ richtext |> map(textInfo => Leaf({...textInfo, fontWeight: Weight.Thin}));
+ let ultralight = (richtext: t) =>
+ richtext
+ |> map(textInfo => Leaf({...textInfo, fontWeight: Weight.UltraLight}));
+ let light = (richtext: t) =>
+ richtext |> map(textInfo => Leaf({...textInfo, fontWeight: Weight.Light}));
+ let normal = (richtext: t) =>
+ richtext
+ |> map(textInfo => Leaf({...textInfo, fontWeight: Weight.Normal}));
+ let medium = (richtext: t) =>
+ richtext
+ |> map(textInfo => Leaf({...textInfo, fontWeight: Weight.Medium}));
+ let semibold = (richtext: t) =>
+ richtext
+ |> map(textInfo => Leaf({...textInfo, fontWeight: Weight.SemiBold}));
+ let bold = (richtext: t) =>
+ richtext |> map(textInfo => Leaf({...textInfo, fontWeight: Weight.Bold}));
+ let ultrabold = (richtext: t) =>
+ richtext
+ |> map(textInfo => Leaf({...textInfo, fontWeight: Weight.UltraBold}));
+ let heavy = (richtext: t) =>
+ richtext |> map(textInfo => Leaf({...textInfo, fontWeight: Weight.Heavy}));
+
+ let fontFamily = (fontFamily: Family.t, richtext: t) =>
+ richtext |> map(textInfo => Leaf({...textInfo, fontFamily}));
+ let italicized = (richtext: t) =>
+ richtext |> map(textInfo => Leaf({...textInfo, italicized: true}));
+ let monospaced = (richtext: t) =>
+ richtext |> map(textInfo => Leaf({...textInfo, monospaced: true}));
+ let fontSize = (fontSize: float, richtext: t) =>
+ richtext |> map(textInfo => Leaf({...textInfo, fontSize}));
+ let color = (color: Color.t, richtext: t) =>
+ richtext |> map(textInfo => Leaf({...textInfo, color}));
+};
+include DSL;
diff --git a/src/UI/RichText.rei b/src/UI/RichText.rei
new file mode 100644
index 000000000..b6a8d01c0
--- /dev/null
+++ b/src/UI/RichText.rei
@@ -0,0 +1,80 @@
+open Revery_Core;
+open Revery_Font;
+
+type textInfo = {
+ fontFamily: Family.t,
+ fontWeight: Weight.t,
+ italicized: bool,
+ monospaced: bool,
+ fontSize: float,
+ text: string,
+ color: Color.t,
+};
+type t =
+ | Leaf(textInfo)
+ | Node(t, t);
+
+let (++): (t, t) => t;
+
+let foldRight: (('acc, textInfo) => 'acc, 'acc, t) => 'acc;
+
+let map: (textInfo => t, t) => t;
+
+let measure: (~smoothing: Smoothing.t=?, t) => Dimensions.t;
+
+module DSL: {
+ let text:
+ (
+ ~fontFamily: Family.t=?,
+ ~fontWeight: Weight.t=?,
+ ~italicized: bool=?,
+ ~monospaced: bool=?,
+ ~fontSize: float=?,
+ ~color: Color.t=?,
+ string
+ ) =>
+ t;
+ let fontWeight: (Weight.t, t) => t;
+ let thin: t => t;
+ let ultralight: t => t;
+ let light: t => t;
+ let normal: t => t;
+ let medium: t => t;
+ let semibold: t => t;
+ let bold: t => t;
+ let ultrabold: t => t;
+ let heavy: t => t;
+
+ let fontFamily: (Family.t, t) => t;
+ let italicized: t => t;
+ let monospaced: t => t;
+ let fontSize: (float, t) => t;
+ let color: (Color.t, t) => t;
+};
+let text:
+ (
+ ~fontFamily: Family.t=?,
+ ~fontWeight: Weight.t=?,
+ ~italicized: bool=?,
+ ~monospaced: bool=?,
+ ~fontSize: float=?,
+ ~color: Color.t=?,
+ string
+ ) =>
+ t;
+let fontWeight: (Weight.t, t) => t;
+let thin: t => t;
+let ultralight: t => t;
+let light: t => t;
+let normal: t => t;
+let medium: t => t;
+let semibold: t => t;
+let bold: t => t;
+let ultrabold: t => t;
+let heavy: t => t;
+
+let fontFamily: (Family.t, t) => t;
+let italicized: t => t;
+let monospaced: t => t;
+let fontSize: (float, t) => t;
+let color: (Color.t, t) => t;
diff --git a/src/UI_Components/Revery_UI_Components.re b/src/UI_Components/Revery_UI_Components.re
index ec547ce55..34370e3a9 100644
--- a/src/UI_Components/Revery_UI_Components.re
+++ b/src/UI_Components/Revery_UI_Components.re
@@ -40,4 +40,5 @@ module RadioButtonsString =
module ClickableText = ClickableText;
module Ticker = Ticker;
module Tree = Tree;
+module RichTextView = RichTextView;
module Markdown = Markdown;
diff --git a/src/UI_Components/RichTextView.re b/src/UI_Components/RichTextView.re
new file mode 100644
index 000000000..a963aa4ab
--- /dev/null
+++ b/src/UI_Components/RichTextView.re
@@ -0,0 +1,31 @@
+open Revery_UI;
+open Revery_UI_Primitives;
+open Revery_Font;
+
+let make =
+ (~style=[], ~smoothing=Smoothing.default, ~richtext: RichText.t, ()) => {
+ let text =
+ RichText.foldRight(
+ (acc, textInfo) =>
+ [
+ ,
+ ...acc,
+ ],
+ [],
+ richtext,
+ );
+
+ // TODO: Add alignItems(`Baseline) if that exists
+
+ {text |> React.listToElement}
+ ;
+};