diff --git a/lib/scenic/color.ex b/lib/scenic/color.ex index 4388adfa..f2d5459b 100644 --- a/lib/scenic/color.ex +++ b/lib/scenic/color.ex @@ -4,157 +4,7 @@ # defmodule Scenic.Color do - @named_colors %{ - alice_blue: {0xF0, 0xF8, 0xFF}, - antique_white: {0xFA, 0xEB, 0xD7}, - aqua: {0x00, 0xFF, 0xFF}, - aquamarine: {0x7F, 0xFF, 0xD4}, - azure: {0xF0, 0xFF, 0xFF}, - beige: {0xF5, 0xF5, 0xDC}, - bisque: {0xFF, 0xE4, 0xC4}, - black: {0x00, 0x00, 0x00}, - blanched_almond: {0xFF, 0xEB, 0xCD}, - blue: {0x00, 0x00, 0xFF}, - blue_violet: {0x8A, 0x2B, 0xE2}, - brown: {0xA5, 0x2A, 0x2A}, - burly_wood: {0xDE, 0xB8, 0x87}, - cadet_blue: {0x5F, 0x9E, 0xA0}, - chartreuse: {0x7F, 0xFF, 0x00}, - chocolate: {0xD2, 0x69, 0x1E}, - coral: {0xFF, 0x7F, 0x50}, - cornflower_blue: {0x64, 0x95, 0xED}, - cornsilk: {0xFF, 0xF8, 0xDC}, - crimson: {0xDC, 0x14, 0x3C}, - cyan: {0x00, 0xFF, 0xFF}, - dark_blue: {0x00, 0x00, 0x8B}, - dark_cyan: {0x00, 0x8B, 0x8B}, - dark_golden_rod: {0xB8, 0x86, 0x0B}, - dark_gray: {0xA9, 0xA9, 0xA9}, - dark_grey: {0xA9, 0xA9, 0xA9}, - dark_green: {0x00, 0x64, 0x00}, - dark_khaki: {0xBD, 0xB7, 0x6B}, - dark_magenta: {0x8B, 0x00, 0x8B}, - dark_olive_green: {0x55, 0x6B, 0x2F}, - dark_orange: {0xFF, 0x8C, 0x00}, - dark_orchid: {0x99, 0x32, 0xCC}, - dark_red: {0x8B, 0x00, 0x00}, - dark_salmon: {0xE9, 0x96, 0x7A}, - dark_sea_green: {0x8F, 0xBC, 0x8F}, - dark_slate_blue: {0x48, 0x3D, 0x8B}, - dark_slate_gray: {0x2F, 0x4F, 0x4F}, - dark_slate_grey: {0x2F, 0x4F, 0x4F}, - dark_turquoise: {0x00, 0xCE, 0xD1}, - dark_violet: {0x94, 0x00, 0xD3}, - deep_pink: {0xFF, 0x14, 0x93}, - deep_sky_blue: {0x00, 0xBF, 0xFF}, - dim_gray: {0x69, 0x69, 0x69}, - dim_grey: {0x69, 0x69, 0x69}, - dodger_blue: {0x1E, 0x90, 0xFF}, - fire_brick: {0xB2, 0x22, 0x22}, - floral_white: {0xFF, 0xFA, 0xF0}, - forest_green: {0x22, 0x8B, 0x22}, - fuchsia: {0xFF, 0x00, 0xFF}, - gainsboro: {0xDC, 0xDC, 0xDC}, - ghost_white: {0xF8, 0xF8, 0xFF}, - gold: {0xFF, 0xD7, 0x00}, - golden_rod: {0xDA, 0xA5, 0x20}, - gray: {0x80, 0x80, 0x80}, - grey: {0x80, 0x80, 0x80}, - green: {0x00, 0x80, 0x00}, - green_yellow: {0xAD, 0xFF, 0x2F}, - honey_dew: {0xF0, 0xFF, 0xF0}, - hot_pink: {0xFF, 0x69, 0xB4}, - indian_red: {0xCD, 0x5C, 0x5C}, - indigo: {0x4B, 0x00, 0x82}, - ivory: {0xFF, 0xFF, 0xF0}, - khaki: {0xF0, 0xE6, 0x8C}, - lavender: {0xE6, 0xE6, 0xFA}, - lavender_blush: {0xFF, 0xF0, 0xF5}, - lawn_green: {0x7C, 0xFC, 0x00}, - lemon_chiffon: {0xFF, 0xFA, 0xCD}, - light_blue: {0xAD, 0xD8, 0xE6}, - light_coral: {0xF0, 0x80, 0x80}, - light_cyan: {0xE0, 0xFF, 0xFF}, - light_golden_rod: {0xFA, 0xFA, 0xD2}, - light_golden_rod_yellow: {0xFA, 0xFA, 0xD2}, - light_gray: {0xD3, 0xD3, 0xD3}, - light_grey: {0xD3, 0xD3, 0xD3}, - light_green: {0x90, 0xEE, 0x90}, - light_pink: {0xFF, 0xB6, 0xC1}, - light_salmon: {0xFF, 0xA0, 0x7A}, - light_sea_green: {0x20, 0xB2, 0xAA}, - light_sky_blue: {0x87, 0xCE, 0xFA}, - light_slate_gray: {0x77, 0x88, 0x99}, - light_slate_grey: {0x77, 0x88, 0x99}, - light_steel_blue: {0xB0, 0xC4, 0xDE}, - light_yellow: {0xFF, 0xFF, 0xE0}, - lime: {0x00, 0xFF, 0x00}, - lime_green: {0x32, 0xCD, 0x32}, - linen: {0xFA, 0xF0, 0xE6}, - magenta: {0xFF, 0x00, 0xFF}, - maroon: {0x80, 0x00, 0x00}, - medium_aqua_marine: {0x66, 0xCD, 0xAA}, - medium_blue: {0x00, 0x00, 0xCD}, - medium_orchid: {0xBA, 0x55, 0xD3}, - medium_purple: {0x93, 0x70, 0xDB}, - medium_sea_green: {0x3C, 0xB3, 0x71}, - medium_slate_blue: {0x7B, 0x68, 0xEE}, - medium_spring_green: {0x00, 0xFA, 0x9A}, - medium_turquoise: {0x48, 0xD1, 0xCC}, - medium_violet_red: {0xC7, 0x15, 0x85}, - midnight_blue: {0x19, 0x19, 0x70}, - mint_cream: {0xF5, 0xFF, 0xFA}, - misty_rose: {0xFF, 0xE4, 0xE1}, - moccasin: {0xFF, 0xE4, 0xB5}, - navajo_white: {0xFF, 0xDE, 0xAD}, - navy: {0x00, 0x00, 0x80}, - old_lace: {0xFD, 0xF5, 0xE6}, - olive: {0x80, 0x80, 0x00}, - olive_drab: {0x6B, 0x8E, 0x23}, - orange: {0xFF, 0xA5, 0x00}, - orange_red: {0xFF, 0x45, 0x00}, - orchid: {0xDA, 0x70, 0xD6}, - pale_golden_rod: {0xEE, 0xE8, 0xAA}, - pale_green: {0x98, 0xFB, 0x98}, - pale_turquoise: {0xAF, 0xEE, 0xEE}, - pale_violet_red: {0xDB, 0x70, 0x93}, - papaya_whip: {0xFF, 0xEF, 0xD5}, - peach_puff: {0xFF, 0xDA, 0xB9}, - peru: {0xCD, 0x85, 0x3F}, - pink: {0xFF, 0xC0, 0xCB}, - plum: {0xDD, 0xA0, 0xDD}, - powder_blue: {0xB0, 0xE0, 0xE6}, - purple: {0x80, 0x00, 0x80}, - rebecca_purple: {0x66, 0x33, 0x99}, - red: {0xFF, 0x00, 0x00}, - rosy_brown: {0xBC, 0x8F, 0x8F}, - royal_blue: {0x41, 0x69, 0xE1}, - saddle_brown: {0x8B, 0x45, 0x13}, - salmon: {0xFA, 0x80, 0x72}, - sandy_brown: {0xF4, 0xA4, 0x60}, - sea_green: {0x2E, 0x8B, 0x57}, - sea_shell: {0xFF, 0xF5, 0xEE}, - sienna: {0xA0, 0x52, 0x2D}, - silver: {0xC0, 0xC0, 0xC0}, - sky_blue: {0x87, 0xCE, 0xEB}, - slate_blue: {0x6A, 0x5A, 0xCD}, - slate_gray: {0x70, 0x80, 0x90}, - slate_grey: {0x70, 0x80, 0x90}, - snow: {0xFF, 0xFA, 0xFA}, - spring_green: {0x00, 0xFF, 0x7F}, - steel_blue: {0x46, 0x82, 0xB4}, - tan: {0xD2, 0xB4, 0x8C}, - teal: {0x00, 0x80, 0x80}, - thistle: {0xD8, 0xBF, 0xD8}, - tomato: {0xFF, 0x63, 0x47}, - turquoise: {0x40, 0xE0, 0xD0}, - violet: {0xEE, 0x82, 0xEE}, - wheat: {0xF5, 0xDE, 0xB3}, - white: {0xFF, 0xFF, 0xFF}, - white_smoke: {0xF5, 0xF5, 0xF5}, - yellow: {0xFF, 0xFF, 0x00}, - yellow_green: {0x9A, 0xCD, 0x32} - } + alias Scenic.Themes @moduledoc """ APIs to create and work with colors. @@ -173,7 +23,7 @@ defmodule Scenic.Color do Most of the time, you will use one of the pre-defined named colors from the Named Colors table. However, there are times when you want to work with - other color formats ranging from simple grayscale to rgb to hsl. + other color formats ranging from simple grayscale to rgb to hsl. The following formats are all supported by the `Scenic.Color` module. The values of r, g, b, and a are integers between 0 and 255. @@ -199,8 +49,6 @@ defmodule Scenic.Color do a list of all the color names. I'll eventually add a link to a page that shows them visually. - #{inspect(Enum.map(@named_colors, fn {k, _v} -> k end) |> Enum.sort(), limit: :infinity, pretty: true)} - ## Additional Named Colors @@ -613,10 +461,11 @@ defmodule Scenic.Color do end # -------------------------------------------------------- + @spec named :: map @doc """ Return map of all named colors and their values """ - def named(), do: @named_colors + def named(), do: Themes.get_palette() # -------------------------------------------------------- # @doc """ diff --git a/lib/scenic/component/button.ex b/lib/scenic/component/button.ex index 2591f6e1..847a3310 100644 --- a/lib/scenic/component/button.ex +++ b/lib/scenic/component/button.ex @@ -131,7 +131,7 @@ defmodule Scenic.Component.Button do alias Scenic.Graph alias Scenic.Scene - alias Scenic.Primitive.Style.Theme + alias Scenic.Themes alias Scenic.Assets.Static import Scenic.Primitives, only: [{:rrect, 3}, {:text, 3}, {:update_opts, 2}] @@ -164,12 +164,15 @@ defmodule Scenic.Component.Button do # theme is passed in as an inherited style theme = case opts[:theme] do - nil -> Theme.preset(:primary) - :dark -> Theme.preset(:primary) - :light -> Theme.preset(:primary) - theme -> theme + nil -> Themes.preset({:scenic, :primary}) + {:scenic, :dark} -> Themes.preset({:scenic, :primary}) + {:scenic, :light} -> Themes.preset({:scenic, :primary}) + theme -> + case Themes.normalize(theme) do + nil -> Themes.preset({:scenic, :primary}) + theme -> theme + end end - |> Theme.normalize() # font related info font = Keyword.get(styles, :font, @default_font) @@ -278,11 +281,11 @@ defmodule Scenic.Component.Button do ) end - defp do_special_theme_outline(graph, :dark, border) do + defp do_special_theme_outline(graph, {:scenic, :dark}, border) do Graph.modify(graph, :btn, &update_opts(&1, stroke: {1, border})) end - defp do_special_theme_outline(graph, :light, border) do + defp do_special_theme_outline(graph, {:scenic, :light}, border) do Graph.modify(graph, :btn, &update_opts(&1, stroke: {1, border})) end diff --git a/lib/scenic/component/input/caret.ex b/lib/scenic/component/input/caret.ex index 91c64890..61d5955d 100644 --- a/lib/scenic/component/input/caret.ex +++ b/lib/scenic/component/input/caret.ex @@ -47,7 +47,7 @@ defmodule Scenic.Component.Input.Caret do ] alias Scenic.Graph - alias Scenic.Primitive.Style.Theme + alias Scenic.Themes @width 2 @inset_v 4 @@ -86,7 +86,7 @@ defmodule Scenic.Component.Input.Caret do case opts[:color] do nil -> opts[:theme] - |> Theme.normalize() + |> Themes.normalize() |> Map.get(:highlight) c -> diff --git a/lib/scenic/component/input/checkbox.ex b/lib/scenic/component/input/checkbox.ex index aed368a5..7a9d5318 100644 --- a/lib/scenic/component/input/checkbox.ex +++ b/lib/scenic/component/input/checkbox.ex @@ -54,7 +54,7 @@ defmodule Scenic.Component.Input.Checkbox do alias Scenic.Graph alias Scenic.Scene alias Scenic.Primitive - alias Scenic.Primitive.Style.Theme + alias Scenic.Themes alias Scenic.Script alias Scenic.Assets.Static @@ -95,8 +95,8 @@ defmodule Scenic.Component.Input.Checkbox do # theme is passed in as an inherited style theme = - (opts[:theme] || Theme.preset(:dark)) - |> Theme.normalize() + (opts[:theme] || Themes.preset({:scenic, :dark})) + |> Themes.normalize() # font related info {:ok, {Static.Font, fm}} = Static.meta(@default_font) diff --git a/lib/scenic/component/input/dropdown.ex b/lib/scenic/component/input/dropdown.ex index 4a767975..b388085e 100644 --- a/lib/scenic/component/input/dropdown.ex +++ b/lib/scenic/component/input/dropdown.ex @@ -84,7 +84,7 @@ defmodule Scenic.Component.Input.Dropdown do alias Scenic.Graph alias Scenic.Scene - alias Scenic.Primitive.Style.Theme + alias Scenic.Themes import Scenic.Primitives alias Scenic.Assets.Static @@ -188,8 +188,8 @@ defmodule Scenic.Component.Input.Dropdown do # theme is passed in as an inherited style theme = - (opts[:theme] || Theme.preset(:dark)) - |> Theme.normalize() + (opts[:theme] || Themes.preset({:scenic, :dark})) + |> Themes.normalize() # font related info {:ok, {Static.Font, fm}} = Static.meta(@default_font) diff --git a/lib/scenic/component/input/radio_button.ex b/lib/scenic/component/input/radio_button.ex index 42e0bf1e..990a86a3 100644 --- a/lib/scenic/component/input/radio_button.ex +++ b/lib/scenic/component/input/radio_button.ex @@ -30,7 +30,7 @@ defmodule Scenic.Component.Input.RadioButton do alias Scenic.Scene alias Scenic.Graph alias Scenic.Primitive - alias Scenic.Primitive.Style.Theme + alias Scenic.Themes alias Scenic.Assets.Static require Logger @@ -70,8 +70,8 @@ defmodule Scenic.Component.Input.RadioButton do def init(scene, {text, id, checked?}, opts) do # theme is passed in as an inherited style theme = - (opts[:theme] || Theme.preset(:dark)) - |> Theme.normalize() + (opts[:theme] || Themes.preset({:scenic, :dark})) + |> Themes.normalize() # font related info {:ok, {Static.Font, fm}} = Static.meta(@default_font) diff --git a/lib/scenic/component/input/slider.ex b/lib/scenic/component/input/slider.ex index fe511735..0bb5a4a5 100644 --- a/lib/scenic/component/input/slider.ex +++ b/lib/scenic/component/input/slider.ex @@ -69,7 +69,7 @@ defmodule Scenic.Component.Input.Slider do use Scenic.Component, has_children: false alias Scenic.Graph - alias Scenic.Primitive.Style.Theme + alias Scenic.Themes import Scenic.Primitives, only: [{:rect, 3}, {:line, 3}, {:rrect, 3}, {:update_opts, 2}] require Logger @@ -192,8 +192,8 @@ defmodule Scenic.Component.Input.Slider do # theme is passed in as an inherited style theme = - (opts[:theme] || Theme.preset(:primary)) - |> Theme.normalize() + (opts[:theme] || Themes.preset({:scenic, :dark})) + |> Themes.normalize() # get button specific styles width = opts[:width] || @default_width diff --git a/lib/scenic/component/input/text_field.ex b/lib/scenic/component/input/text_field.ex index 1563476b..d7369eac 100644 --- a/lib/scenic/component/input/text_field.ex +++ b/lib/scenic/component/input/text_field.ex @@ -80,7 +80,7 @@ defmodule Scenic.Component.Input.TextField do alias Scenic.Graph alias Scenic.Component.Input.Caret - alias Scenic.Primitive.Style.Theme + alias Scenic.Themes # alias Scenic.Assets.Static require Logger @@ -138,8 +138,8 @@ defmodule Scenic.Component.Input.TextField do # theme is passed in as an inherited style theme = - (opts[:theme] || Theme.preset(:dark)) - |> Theme.normalize() + (opts[:theme] || Themes.preset({:scenic, :dark})) + |> Themes.normalize() # get the text_field specific opts hint = opts[:hint] || @default_hint diff --git a/lib/scenic/component/input/toggle.ex b/lib/scenic/component/input/toggle.ex index 9c0aa0cd..dd659974 100644 --- a/lib/scenic/component/input/toggle.ex +++ b/lib/scenic/component/input/toggle.ex @@ -59,7 +59,7 @@ defmodule Scenic.Component.Input.Toggle do alias Scenic.Graph alias Scenic.Primitive alias Scenic.Primitive.Group - alias Scenic.Primitive.Style.Theme + alias Scenic.Themes alias Scenic.ViewPort import Scenic.Primitives @@ -131,7 +131,7 @@ defmodule Scenic.Component.Input.Toggle do # theme is passed in as an inherited style theme = opts[:theme] - |> Theme.normalize() + |> Themes.normalize() # get toggle specific opts thumb_radius = Keyword.get(opts, :thumb_radius, @default_thumb_radius) diff --git a/lib/scenic/graph/compiler.ex b/lib/scenic/graph/compiler.ex index 45bebe88..6a6f5a65 100644 --- a/lib/scenic/graph/compiler.ex +++ b/lib/scenic/graph/compiler.ex @@ -12,7 +12,7 @@ defmodule Scenic.Graph.Compiler do alias Scenic.Primitive alias Scenic.Graph alias Scenic.Color - alias Scenic.Primitive.Style.Theme + alias Scenic.Themes alias Scenic.Graph.Compiler # import IEx @@ -271,7 +271,7 @@ defmodule Scenic.Graph.Compiler do defp do_text_color(ops, %{reqs: %{theme: theme}} = state) do color = theme - |> Theme.normalize() + |> Themes.normalize() |> Map.get(:text) |> Color.to_rgba() diff --git a/lib/scenic/palette.ex b/lib/scenic/palette.ex new file mode 100644 index 00000000..8177600f --- /dev/null +++ b/lib/scenic/palette.ex @@ -0,0 +1,155 @@ +defmodule Scenic.Palette do + @palette %{ + alice_blue: {0xF0, 0xF8, 0xFF}, + antique_white: {0xFA, 0xEB, 0xD7}, + aqua: {0x00, 0xFF, 0xFF}, + aquamarine: {0x7F, 0xFF, 0xD4}, + azure: {0xF0, 0xFF, 0xFF}, + beige: {0xF5, 0xF5, 0xDC}, + bisque: {0xFF, 0xE4, 0xC4}, + black: {0x00, 0x00, 0x00}, + blanched_almond: {0xFF, 0xEB, 0xCD}, + blue: {0x00, 0x00, 0xFF}, + blue_violet: {0x8A, 0x2B, 0xE2}, + brown: {0xA5, 0x2A, 0x2A}, + burly_wood: {0xDE, 0xB8, 0x87}, + cadet_blue: {0x5F, 0x9E, 0xA0}, + chartreuse: {0x7F, 0xFF, 0x00}, + chocolate: {0xD2, 0x69, 0x1E}, + coral: {0xFF, 0x7F, 0x50}, + cornflower_blue: {0x64, 0x95, 0xED}, + cornsilk: {0xFF, 0xF8, 0xDC}, + crimson: {0xDC, 0x14, 0x3C}, + cyan: {0x00, 0xFF, 0xFF}, + dark_blue: {0x00, 0x00, 0x8B}, + dark_cyan: {0x00, 0x8B, 0x8B}, + dark_golden_rod: {0xB8, 0x86, 0x0B}, + dark_gray: {0xA9, 0xA9, 0xA9}, + dark_grey: {0xA9, 0xA9, 0xA9}, + dark_green: {0x00, 0x64, 0x00}, + dark_khaki: {0xBD, 0xB7, 0x6B}, + dark_magenta: {0x8B, 0x00, 0x8B}, + dark_olive_green: {0x55, 0x6B, 0x2F}, + dark_orange: {0xFF, 0x8C, 0x00}, + dark_orchid: {0x99, 0x32, 0xCC}, + dark_red: {0x8B, 0x00, 0x00}, + dark_salmon: {0xE9, 0x96, 0x7A}, + dark_sea_green: {0x8F, 0xBC, 0x8F}, + dark_slate_blue: {0x48, 0x3D, 0x8B}, + dark_slate_gray: {0x2F, 0x4F, 0x4F}, + dark_slate_grey: {0x2F, 0x4F, 0x4F}, + dark_turquoise: {0x00, 0xCE, 0xD1}, + dark_violet: {0x94, 0x00, 0xD3}, + deep_pink: {0xFF, 0x14, 0x93}, + deep_sky_blue: {0x00, 0xBF, 0xFF}, + dim_gray: {0x69, 0x69, 0x69}, + dim_grey: {0x69, 0x69, 0x69}, + dodger_blue: {0x1E, 0x90, 0xFF}, + fire_brick: {0xB2, 0x22, 0x22}, + floral_white: {0xFF, 0xFA, 0xF0}, + forest_green: {0x22, 0x8B, 0x22}, + fuchsia: {0xFF, 0x00, 0xFF}, + gainsboro: {0xDC, 0xDC, 0xDC}, + ghost_white: {0xF8, 0xF8, 0xFF}, + gold: {0xFF, 0xD7, 0x00}, + golden_rod: {0xDA, 0xA5, 0x20}, + gray: {0x80, 0x80, 0x80}, + grey: {0x80, 0x80, 0x80}, + green: {0x00, 0x80, 0x00}, + green_yellow: {0xAD, 0xFF, 0x2F}, + honey_dew: {0xF0, 0xFF, 0xF0}, + hot_pink: {0xFF, 0x69, 0xB4}, + indian_red: {0xCD, 0x5C, 0x5C}, + indigo: {0x4B, 0x00, 0x82}, + ivory: {0xFF, 0xFF, 0xF0}, + khaki: {0xF0, 0xE6, 0x8C}, + lavender: {0xE6, 0xE6, 0xFA}, + lavender_blush: {0xFF, 0xF0, 0xF5}, + lawn_green: {0x7C, 0xFC, 0x00}, + lemon_chiffon: {0xFF, 0xFA, 0xCD}, + light_blue: {0xAD, 0xD8, 0xE6}, + light_coral: {0xF0, 0x80, 0x80}, + light_cyan: {0xE0, 0xFF, 0xFF}, + light_golden_rod: {0xFA, 0xFA, 0xD2}, + light_golden_rod_yellow: {0xFA, 0xFA, 0xD2}, + light_gray: {0xD3, 0xD3, 0xD3}, + light_grey: {0xD3, 0xD3, 0xD3}, + light_green: {0x90, 0xEE, 0x90}, + light_pink: {0xFF, 0xB6, 0xC1}, + light_salmon: {0xFF, 0xA0, 0x7A}, + light_sea_green: {0x20, 0xB2, 0xAA}, + light_sky_blue: {0x87, 0xCE, 0xFA}, + light_slate_gray: {0x77, 0x88, 0x99}, + light_slate_grey: {0x77, 0x88, 0x99}, + light_steel_blue: {0xB0, 0xC4, 0xDE}, + light_yellow: {0xFF, 0xFF, 0xE0}, + lime: {0x00, 0xFF, 0x00}, + lime_green: {0x32, 0xCD, 0x32}, + linen: {0xFA, 0xF0, 0xE6}, + magenta: {0xFF, 0x00, 0xFF}, + maroon: {0x80, 0x00, 0x00}, + medium_aqua_marine: {0x66, 0xCD, 0xAA}, + medium_blue: {0x00, 0x00, 0xCD}, + medium_orchid: {0xBA, 0x55, 0xD3}, + medium_purple: {0x93, 0x70, 0xDB}, + medium_sea_green: {0x3C, 0xB3, 0x71}, + medium_slate_blue: {0x7B, 0x68, 0xEE}, + medium_spring_green: {0x00, 0xFA, 0x9A}, + medium_turquoise: {0x48, 0xD1, 0xCC}, + medium_violet_red: {0xC7, 0x15, 0x85}, + midnight_blue: {0x19, 0x19, 0x70}, + mint_cream: {0xF5, 0xFF, 0xFA}, + misty_rose: {0xFF, 0xE4, 0xE1}, + moccasin: {0xFF, 0xE4, 0xB5}, + navajo_white: {0xFF, 0xDE, 0xAD}, + navy: {0x00, 0x00, 0x80}, + old_lace: {0xFD, 0xF5, 0xE6}, + olive: {0x80, 0x80, 0x00}, + olive_drab: {0x6B, 0x8E, 0x23}, + orange: {0xFF, 0xA5, 0x00}, + orange_red: {0xFF, 0x45, 0x00}, + orchid: {0xDA, 0x70, 0xD6}, + pale_golden_rod: {0xEE, 0xE8, 0xAA}, + pale_green: {0x98, 0xFB, 0x98}, + pale_turquoise: {0xAF, 0xEE, 0xEE}, + pale_violet_red: {0xDB, 0x70, 0x93}, + papaya_whip: {0xFF, 0xEF, 0xD5}, + peach_puff: {0xFF, 0xDA, 0xB9}, + peru: {0xCD, 0x85, 0x3F}, + pink: {0xFF, 0xC0, 0xCB}, + plum: {0xDD, 0xA0, 0xDD}, + powder_blue: {0xB0, 0xE0, 0xE6}, + purple: {0x80, 0x00, 0x80}, + rebecca_purple: {0x66, 0x33, 0x99}, + red: {0xFF, 0x00, 0x00}, + rosy_brown: {0xBC, 0x8F, 0x8F}, + royal_blue: {0x41, 0x69, 0xE1}, + saddle_brown: {0x8B, 0x45, 0x13}, + salmon: {0xFA, 0x80, 0x72}, + sandy_brown: {0xF4, 0xA4, 0x60}, + sea_green: {0x2E, 0x8B, 0x57}, + sea_shell: {0xFF, 0xF5, 0xEE}, + sienna: {0xA0, 0x52, 0x2D}, + silver: {0xC0, 0xC0, 0xC0}, + sky_blue: {0x87, 0xCE, 0xEB}, + slate_blue: {0x6A, 0x5A, 0xCD}, + slate_gray: {0x70, 0x80, 0x90}, + slate_grey: {0x70, 0x80, 0x90}, + snow: {0xFF, 0xFA, 0xFA}, + spring_green: {0x00, 0xFF, 0x7F}, + steel_blue: {0x46, 0x82, 0xB4}, + tan: {0xD2, 0xB4, 0x8C}, + teal: {0x00, 0x80, 0x80}, + thistle: {0xD8, 0xBF, 0xD8}, + tomato: {0xFF, 0x63, 0x47}, + turquoise: {0x40, 0xE0, 0xD0}, + violet: {0xEE, 0x82, 0xEE}, + wheat: {0xF5, 0xDE, 0xB3}, + white: {0xFF, 0xFF, 0xFF}, + white_smoke: {0xF5, 0xF5, 0xF5}, + yellow: {0xFF, 0xFF, 0x00}, + yellow_green: {0x9A, 0xCD, 0x32} + } + + def get(), do: @palette +end diff --git a/lib/scenic/primitive/style/style.ex b/lib/scenic/primitive/style/style.ex index f709392d..2e79690d 100644 --- a/lib/scenic/primitive/style/style.ex +++ b/lib/scenic/primitive/style/style.ex @@ -49,6 +49,7 @@ defmodule Scenic.Primitive.Style do """ alias Scenic.Primitive.Style + alias Scenic.Themes # import IEx @@ -85,7 +86,7 @@ defmodule Scenic.Primitive.Style do :stroke => Style.Stroke, :text_align => Style.TextAlign, :text_base => Style.TextBase, - :theme => Style.Theme + :theme => Themes } @valid_styles @opts_map @@ -106,7 +107,7 @@ defmodule Scenic.Primitive.Style do stroke: [type: {:custom, Style.Stroke, :validate, []}], text_align: [type: {:custom, Style.TextAlign, :validate, []}], text_base: [type: {:custom, Style.TextBase, :validate, []}], - theme: [type: {:custom, Style.Theme, :validate, []}] + theme: [type: {:custom, Themes, :validate, []}] ] @callback validate(data :: any) :: {:ok, data :: any} | {:error, String.t()} diff --git a/lib/scenic/primitive/style/theme.ex b/lib/scenic/primitive/style/theme.ex deleted file mode 100644 index 6da4d952..00000000 --- a/lib/scenic/primitive/style/theme.ex +++ /dev/null @@ -1,187 +0,0 @@ -# -# Created by Boyd Multerer on 2018-08-18. -# Copyright © 2018 Kry10 Limited. All rights reserved. -# - -defmodule Scenic.Primitive.Style.Theme do - @moduledoc """ - Themes are a way to bundle up a set of colors that are intended to be used - by components invoked by a scene. - - There are a set of pre-defined themes. - You can also pass in a map of color values. - - Unlike other styles, The currently set theme is given to child components. - Each component gets to pick, choose, or ignore any colors in a given style. - - ### Predefined Themes - * `:dark` - This is the default and most common. Use when the background is dark. - * `:light` - Use when the background is light colored. - - ### Specialty Themes - - The remaining themes are designed to color the standard components and don't really - make much sense when applied to the root of a graph. You could, but it would be... - interesting. - - The most obvious place to use them is with [`Button`](Scenic.Component.Button.html) - components. - - * `:primary` - Blue background. This is the primary button type indicator. - * `:secondary` - Grey background. Not primary type indicator. - * `:success` - Green background. - * `:danger` - Red background. Use for irreversible or dangerous actions. - * `:warning` - Orange background. - * `:info` - Lightish blue background. - * `:text` - Transparent background. - """ - - use Scenic.Primitive.Style - alias Scenic.Primitive.Style.Paint.Color - - @theme_light %{ - text: :black, - background: :white, - border: :dark_grey, - active: {215, 215, 215}, - thumb: :cornflower_blue, - focus: :blue, - highlight: :saddle_brown - } - - @theme_dark %{ - text: :white, - background: :black, - border: :light_grey, - active: {40, 40, 40}, - thumb: :cornflower_blue, - focus: :cornflower_blue, - highlight: :sandy_brown - } - - # specialty themes - @primary Map.merge(@theme_dark, %{background: {72, 122, 252}, active: {58, 94, 201}}) - @secondary Map.merge(@theme_dark, %{background: {111, 117, 125}, active: {86, 90, 95}}) - @success Map.merge(@theme_dark, %{background: {99, 163, 74}, active: {74, 123, 56}}) - @danger Map.merge(@theme_dark, %{background: {191, 72, 71}, active: {164, 54, 51}}) - @warning Map.merge(@theme_light, %{background: {239, 196, 42}, active: {197, 160, 31}}) - @info Map.merge(@theme_dark, %{background: {94, 159, 183}, active: {70, 119, 138}}) - @text Map.merge(@theme_dark, %{text: {72, 122, 252}, background: :clear, active: :clear}) - - @themes %{ - light: @theme_light, - dark: @theme_dark, - primary: @primary, - secondary: @secondary, - success: @success, - danger: @danger, - warning: @warning, - info: @info, - text: @text - } - - # ============================================================================ - # data verification and serialization - @doc false - def validate(theme) - def validate(:light), do: {:ok, :light} - def validate(:dark), do: {:ok, :dark} - def validate(:primary), do: {:ok, :primary} - def validate(:secondary), do: {:ok, :secondary} - def validate(:success), do: {:ok, :success} - def validate(:danger), do: {:ok, :danger} - def validate(:warning), do: {:ok, :warning} - def validate(:info), do: {:ok, :info} - def validate(:text), do: {:ok, :text} - - def validate( - %{ - text: _, - background: _, - border: _, - active: _, - thumb: _, - focus: _ - } = theme - ) do - # we know all the required colors are there. - # now make sure they are all valid colors, including any custom added ones. - theme - |> Enum.reduce({:ok, theme}, fn - _, {:error, msg} -> - {:error, msg} - - {key, color}, {:ok, _} = acc -> - case Color.validate(color) do - {:ok, _} -> acc - {:error, msg} -> err_color(key, msg) - end - end) - end - - def validate(name) when is_atom(name) do - { - :error, - """ - #{IO.ANSI.red()}Invalid theme name - Received: #{inspect(name)} - #{IO.ANSI.yellow()} - Named themes must be from the following list: - :light, :dark, :primary, :secondary, :success, :danger, :warning, :info, :text#{IO.ANSI.default_color()} - """ - } - end - - def validate(%{} = map) do - { - :error, - """ - #{IO.ANSI.red()}Invalid theme specification - Received: #{inspect(map)} - #{IO.ANSI.yellow()} - You passed in a map, but it didn't include all the required color specifications. - It must contain a valid color for each of the following entries. - :text, :background, :border, :active, :thumb, :focus - #{IO.ANSI.default_color()} - """ - } - end - - def validate(data) do - { - :error, - """ - #{IO.ANSI.red()}Invalid theme specification - Received: #{inspect(data)} - #{IO.ANSI.yellow()} - Themes can be a name from this list: - :light, :dark, :primary, :secondary, :success, :danger, :warning, :info, :text - - Or it may also be a map defining colors for the values of - :text, :background, :border, :active, :thumb, :focus - - If you pass in a map, you may add your own colors in addition to the required ones.#{IO.ANSI.default_color()} - """ - } - end - - defp err_color(key, msg) do - { - :error, - """ - #{IO.ANSI.red()}Invalid color in map - Map entry: #{inspect(key)} - #{msg} - """ - } - end - - # -------------------------------------------------------- - @doc false - def normalize(theme) when is_atom(theme), do: Map.get(@themes, theme) - def normalize(theme) when is_map(theme), do: theme - - # -------------------------------------------------------- - @doc false - def preset(theme), do: Map.get(@themes, theme) -end diff --git a/lib/scenic/scenes/error.ex b/lib/scenic/scenes/error.ex index 41503bb1..fbece629 100644 --- a/lib/scenic/scenes/error.ex +++ b/lib/scenic/scenes/error.ex @@ -66,7 +66,7 @@ defmodule Scenic.Scenes.Error do graph = Graph.build(font: @default_font, font_size: @size, translate: {@margin_h, @margin_v}) - |> button("Try Again", id: :try_again, theme: :warning) + |> button("Try Again", id: :try_again, theme: {:scenic, :warning}) |> text(head_msg, translate: {0, head_v}, font_size: @size + 4) |> text(args_msg, translate: {0, args_v}, fill: @args_color) |> text(err_msg, translate: {0, err_v}, fill: @error_color) diff --git a/lib/scenic/themes.ex b/lib/scenic/themes.ex new file mode 100644 index 00000000..4580db42 --- /dev/null +++ b/lib/scenic/themes.ex @@ -0,0 +1,443 @@ +defmodule Scenic.Themes do + alias Scenic.Palette + alias Scenic.Primitive.Style.Paint.Color + @moduledoc """ + Manages theme libraries by registering your map of themes to a library key. + By registering themes in this way you can safely pull in themes from external libraries, + without theme names colliding, as well as get all the validation. + + You can add additional keys to be validated on your custom themes by returning a tuple with your map of themes and a list of keys to be validated + from your load function. + + All themes will validate against the default schema. If you provide additional keys, the list will get merged with the list of default keys. + + ### Required Configuration + Setting up themes requires some initial setup. + + Example: + + ```elixir + defmodule MyApplication.Themes do + @themes %{ + light: @theme_light, + dark: @theme_dark, + primary: @primary, + secondary: @secondary, + success: @success, + danger: @danger, + warning: @warning, + info: @info, + text: @text + } + + schema [:surface] # add additional required keys to your theme + + use Scenic.Themes, + [ + {:scenic, Scenic.Themes}, + {:my_app, load()} + ] + + def load(), do: [name: :my_library, themes: @themes, schema: @schema, palette: @palette] + end + ``` + + After the Themes modules has been defined you need to configure it in your config file.any() + + ```elixir + config :scenic, :themes, + module: MyApplication.Themes + ``` + + Now themes are passed around scenic in the form of `{:library_name, :theme_name}` as opposed to just :theme_name. + """ + @callback load() :: keyword + @optional_callbacks load: 0 + + defmacro __using__(sources \\ []) do + quote do + @behaviour Scenic.Themes + @opts_schema [ + name: [required: true, type: :atom], + themes: [required: true, type: :any], + schema: [required: false, type: :any], + palette: [required: false, type: :any] + ] + @default_schema [:text, :background, :border, :active, :thumb, :focus] + @_palette %{} + + @library_themes Enum.reduce(unquote(sources), %{}, fn lib_opts, acc -> + case NimbleOptions.validate(lib_opts, @opts_schema) do + {:ok, lib_opts} -> + name = lib_opts[:name] + themes = lib_opts[:themes] + schema = lib_opts[:schema] || [] + palette = lib_opts[:palette] || %{} + @_palette Map.merge(@_palette, palette) + case themes do + themes when is_map(themes) -> + # not a module so we can load in the settings directly + Map.put_new(acc, name, {themes, List.flatten([@default_schema | schema])}) + themes -> + # this is a module so we have to load the settings + lib_opts = themes.load() + themes = lib_opts[:themes] + schema = lib_opts[:schema] || [] + palette = lib_opts[:palette] || %{} + @_palette Map.merge(@_palette, palette) + Map.put_new(acc, name, {themes, List.flatten([@default_schema | schema])}) + end + {:error, error} -> + raise Exception.message(error) + end + end) + + # validate the passed options + def library(), do: @library_themes + + def _get_palette(), do: @_palette + end + end + + @doc false + def module() do + with {:ok, config} <- Application.fetch_env(:scenic, :themes), + {:ok, module} <- Keyword.fetch(config, :module) do + module + else + _ -> + # No module configure return the default + __MODULE__ + end + end + + def validate(theme) + def validate({lib, theme_name} = lib_theme) when is_atom(theme_name) do + themes = module().library() + case Map.get(themes, lib) do + {themes, schema} -> + # validate against the schema + case Map.get(themes, theme_name) do + nil -> + { + :error, + """ + #{IO.ANSI.red()}Invalid theme specification + Received: #{inspect(theme_name)} + #{IO.ANSI.yellow()} + The theme could not be found in library #{inspect(lib)}. + Ensure you got the name correct. + #{IO.ANSI.default_color()} + """ + } + theme -> + case validate(theme, schema) do + {:ok, _} -> {:ok, lib_theme} + error -> error + end + end + nil -> + { + :error, + """ + #{IO.ANSI.red()}Invalid theme specification + Received: #{inspect(lib_theme)} + #{IO.ANSI.yellow()} + You passed in a tuple representing a library theme, but it could not be found. + Please ensure you've imported the the library correctly in your Themes module. + #{IO.ANSI.default_color()} + """ + } + end + end + + def validate(theme_name) when is_atom(theme_name) do + lib = module().library() + {themes, schema} = Map.get(lib, :scenic) + case Map.get(themes, theme_name) do + nil -> + { + :error, + """ + #{IO.ANSI.red()}Invalid theme specification + Received: #{inspect(theme_name)} + #{IO.ANSI.yellow()} + The theme could not be found in library #{inspect(:scenic)}. + Ensure you got the name correct. + #{IO.ANSI.default_color()} + """ + } + theme -> + case validate(theme, schema) do + {:ok, _} -> {:ok, theme_name} + error -> error + end + end + end + + def validate( + %{ + text: _, + background: _, + border: _, + active: _, + thumb: _, + focus: _ + } = theme + ) do + # we dont have the schema so validate against the default, + # this is not ideal, but should be fine for now. + # we know all the required colors are there. + # now make sure they are all valid colors, including any custom added ones. + theme + |> Enum.reduce({:ok, theme}, fn + _, {:error, msg} -> + {:error, msg} + + {key, color}, {:ok, _} = acc -> + case Color.validate(color) do + {:ok, _} -> acc + {:error, msg} -> err_color(key, msg) + end + end) + end + + def validate(%{} = map) do + { + :error, + """ + #{IO.ANSI.red()}Invalid theme specification + Received: #{inspect(map)} + #{IO.ANSI.yellow()} + You passed in a map, but it didn't include all the required color specifications. + It must contain a valid color for each of the following entries. + :text, :background, :border, :active, :thumb, :focus + If you're using a custom theme please check the documentation for that specific theme. + #{IO.ANSI.default_color()} + """ + } + end + + def validate(data) do + { + :error, + """ + #{IO.ANSI.red()}Invalid theme specification + Received: #{inspect(data)} + #{IO.ANSI.yellow()} + Themes can be a tuple representing a theme for example: + {:scenic, :light}, {:scenic, :dark} + + Or an atom representing one of scenics default themes: + :primary, :secondary + + Or it may also be a map defining colors for the values of + :text, :background, :border, :active, :thumb, :focus + + If you pass in a map, you may add your own colors in addition to the required ones.#{IO.ANSI.default_color()} + """ + } + end + + def validate( + {lib, theme_name} = lib_theme, + schema + ) do + # we have the schema so we can validate against it. + themes = module().library() + case Map.get(themes, lib) do + {themes, _schema} -> + case Map.get(themes, theme_name) do + nil -> + { + :error, + """ + #{IO.ANSI.red()}Invalid theme specification + Received: #{inspect(lib_theme)} + #{IO.ANSI.yellow()} + The theme could not be found in library #{inspect(:scenic)}. + Ensure you got the name correct. + #{IO.ANSI.default_color()} + """ + } + theme -> + schema + |> Enum.reduce({:ok, theme}, fn + _, {:error, msg} -> + {:error, msg} + key, {:ok, _} = acc -> + case Map.has_key?(theme, key) do + true -> acc + false -> err_key(key, theme) + end + end) + end + nil -> + { + :error, + """ + #{IO.ANSI.red()}Invalid theme specification + Received: #{inspect(lib_theme)} + #{IO.ANSI.yellow()} + You passed in a tuple representing a library theme, but it could not be found. + Please ensure you've imported the the library correctly in your Themes module. + #{IO.ANSI.default_color()} + """ + } + end + end + + def validate( + theme, + schema + ) do + # we have the schema so we can validate against it. + schema + |> Enum.reduce({:ok, theme}, fn + _, {:error, msg} -> + {:error, msg} + key, {:ok, _} = acc -> + case Map.has_key?(theme, key) do + true -> acc + false -> err_key(key, theme) + end + end) + end + + @spec get_schema(atom) :: list + @doc """ + Retrieve a library's schema + """ + def get_schema(lib) do + themes = module().library() + case Map.get(themes, lib) do + {_, schema} -> schema + nil -> nil + end + end + + @spec get_palette() :: map + @doc """ + Retrieve the color palette + """ + def get_palette() do + module = module() + if function_exported?(module, :_get_palette, 0), do: apply(module, :_get_palette, []), else: _get_palette() + end + + @spec normalize({atom, atom} | map | atom) :: map | nil + @doc """ + Converts a theme from it's tuple form to it's map form. + """ + def normalize({lib, theme_name}) when is_atom(theme_name) do + themes = module().library() + case Map.get(themes, lib) do + {themes, _schema} -> Map.get(themes, theme_name) + nil -> nil + end + end + + def normalize(theme_name) when is_atom(theme_name) do + themes = module().library() + case Map.get(themes, :scenic) do + {themes, _schema} -> Map.get(themes, theme_name) + nil -> nil + end + end + + def normalize(theme) when is_map(theme), do: theme + + @spec preset({atom, atom} | map | atom) :: map | nil + @doc """ + Get a theme. + """ + def preset({lib, theme_name}) do + themes = module().library() + case Map.get(themes, lib) do + {themes, _schema} -> Map.get(themes, theme_name) + nil -> nil + end + end + + def preset(theme_name) when is_atom(theme_name) do + themes = module().library() + case Map.get(themes, :scenic) do + {themes, _schema} -> Map.get(themes, theme_name) + nil -> nil + end + end + + def preset(theme) when is_map(theme), do: theme + + @theme_light %{ + text: :black, + background: :white, + border: :dark_grey, + active: {215, 215, 215}, + thumb: :cornflower_blue, + focus: :blue, + highlight: :saddle_brown + } + + @theme_dark %{ + text: :white, + background: :black, + border: :light_grey, + active: {40, 40, 40}, + thumb: :cornflower_blue, + focus: :cornflower_blue, + highlight: :sandy_brown + } + + @primary Map.merge(@theme_dark, %{background: {72, 122, 252}, active: {58, 94, 201}}) + @secondary Map.merge(@theme_dark, %{background: {111, 117, 125}, active: {86, 90, 95}}) + @success Map.merge(@theme_dark, %{background: {99, 163, 74}, active: {74, 123, 56}}) + @danger Map.merge(@theme_dark, %{background: {191, 72, 71}, active: {164, 54, 51}}) + @warning Map.merge(@theme_light, %{background: {239, 196, 42}, active: {197, 160, 31}}) + @info Map.merge(@theme_dark, %{background: {94, 159, 183}, active: {70, 119, 138}}) + @text Map.merge(@theme_dark, %{text: {72, 122, 252}, background: :clear, active: :clear}) + @themes %{ + light: @theme_light, + dark: @theme_dark, + primary: @primary, + secondary: @secondary, + success: @success, + danger: @danger, + warning: @warning, + info: @info, + text: @text + } + @default_schema [:text, :background, :border, :active, :thumb, :focus] + @palette Palette.get() + + def _get_palette(), do: @palette + + def library(), do: %{scenic: {@themes, @default_schema}} + + @doc false + def load(), do: [name: :scenic, themes: @themes, palette: @palette] + + defp err_key(key, map) do + { + :error, + """ + #{IO.ANSI.red()}Invalid theme specification + Received: #{inspect(map)} + #{IO.ANSI.yellow()} + Map entry: #{inspect(key)} + #{IO.ANSI.default_color()} + """ + } + end + + defp err_color(key, msg) do + { + :error, + """ + #{IO.ANSI.red()}Invalid color in map + Map entry: #{inspect(key)} + #{msg} + """ + } + end +end diff --git a/lib/scenic/view_port.ex b/lib/scenic/view_port.ex index 4f9c5636..1e3cd155 100644 --- a/lib/scenic/view_port.ex +++ b/lib/scenic/view_port.ex @@ -19,7 +19,7 @@ defmodule Scenic.ViewPort do # alias Scenic.Utilities alias Scenic.Utilities.Validators - alias Scenic.Primitive.Style.Theme + alias Scenic.Themes require Logger @@ -118,7 +118,7 @@ defmodule Scenic.ViewPort do required: true, type: {:custom, Validators, :validate_scene, [:default_scene]} ], - theme: [type: {:custom, Theme, :validate, []}, default: :dark], + theme: [type: {:custom, Themes, :validate, []}, default: {:scenic, :dark}], drivers: [type: {:custom, Driver, :validate, []}, default: []], input_filter: [type: {:custom, __MODULE__, :validate_input_filter, []}, default: :all], opts: [ @@ -395,7 +395,7 @@ defmodule Scenic.ViewPort do def set_theme(viewport, theme) def set_theme(%ViewPort{pid: pid}, theme) do - case Theme.validate(theme) do + case Themes.validate(theme) do # {:ok, theme} -> GenServer.cast( pid, {:set_theme, theme} ) {:ok, theme} -> GenServer.call(pid, {:set_theme, theme}) err -> err @@ -516,7 +516,7 @@ defmodule Scenic.ViewPort do # ets table for scripts. Public. Readable and Writable by others. The intended # use is that Scenes compile graphs in their own process and insert the scripts # in parallel to each other. (Trying to avoid serializing the VP on large messages) - # containing either script of graph data. The scripts can be read by multiple + # containing either script of graph data. The scripts can be read by multiple # drivers at the same time, so is read parallel optimized. If the public write # becomes problematic, the next step is to have the scripts compile, then send # finished scripts to the VP for writing. @@ -847,7 +847,7 @@ defmodule Scenic.ViewPort do # get the background from the theme background = theme - |> Theme.normalize() + |> Themes.normalize() |> Map.get(:background) send(pid, {@clear_color, background}) @@ -1171,7 +1171,7 @@ defmodule Scenic.ViewPort do background = theme - |> Theme.normalize() + |> Themes.normalize() |> Map.get(:background) case DynamicSupervisor.start_child(driver_sup, {Driver, {info, opts}}) do @@ -1188,7 +1188,7 @@ defmodule Scenic.ViewPort do # get the background from the theme background = theme - |> Theme.normalize() + |> Themes.normalize() |> Map.get(:background) # tell the drivers the background changed @@ -1641,7 +1641,7 @@ defmodule Scenic.ViewPort do # skip script primitives - no input handlers there defp comp_input_prim(input, _uid, %Primitive{module: Primitive.Script}, _, _tx), do: input - # it is a group. Calc the local transform if there one, but doesn't go into the + # it is a group. Calc the local transform if there one, but doesn't go into the # list as a component itself... defp comp_input_prim( input, diff --git a/test/scenic/graph/compile_test.exs b/test/scenic/graph/compile_test.exs index c65f7ed7..ba3b3fba 100644 --- a/test/scenic/graph/compile_test.exs +++ b/test/scenic/graph/compile_test.exs @@ -76,7 +76,7 @@ defmodule Scenic.Graph.CompilerTest do # --------------------------------------------------------- test "simple_theme graph works" do {:ok, list} = - Graph.build(font: :roboto, font_size: 26, theme: :dark) + Graph.build(font: :roboto, font_size: 26, theme: {:scenic, :dark}) |> text("theme") |> Compiler.compile() @@ -250,7 +250,7 @@ defmodule Scenic.Graph.CompilerTest do end # --------------------------------------------------------- - # Should correctly compile fill and stroke. Note that + # Should correctly compile fill and stroke. Note that # primitives with neither fill nor stroke are eliminated completely test "fill and stroke are compiled correctly" do {:ok, list} = @@ -380,7 +380,7 @@ defmodule Scenic.Graph.CompilerTest do # --------------------------------------------------------- test "font_styles graph works" do {:ok, list} = - Graph.build(theme: :dark) + Graph.build(theme: {:scenic, :dark}) |> text("roboto", font: :roboto) |> text("size_64", font_size: 64) |> text("left", text_align: :left) diff --git a/test/scenic/primitive/style/theme_test.exs b/test/scenic/primitive/style/theme_test.exs deleted file mode 100644 index ee811847..00000000 --- a/test/scenic/primitive/style/theme_test.exs +++ /dev/null @@ -1,68 +0,0 @@ -# -# Created by Boyd Multerer on 2018-09-25. -# Copyright © 2018-2021 Kry10 Limited. All rights reserved. -# - -defmodule Scenic.Primitive.Style.ThemeTest do - use ExUnit.Case, async: true - doctest Scenic.Primitive.Style.Theme - - alias Scenic.Primitive.Style.Theme - - test "validate accepts the named themes" do - assert Theme.validate(:dark) == {:ok, :dark} - assert Theme.validate(:light) == {:ok, :light} - assert Theme.validate(:primary) == {:ok, :primary} - assert Theme.validate(:secondary) == {:ok, :secondary} - assert Theme.validate(:success) == {:ok, :success} - assert Theme.validate(:danger) == {:ok, :danger} - assert Theme.validate(:warning) == {:ok, :warning} - assert Theme.validate(:info) == {:ok, :info} - assert Theme.validate(:text) == {:ok, :text} - end - - test "validate rejects invalid theme names" do - {:error, msg} = Theme.validate(:invalid) - assert msg =~ "Named themes must be from the following list" - end - - test "validate accepts maps of colors" do - color_map = %{ - text: :red, - background: :green, - border: :blue, - active: :magenta, - thumb: :cyan, - focus: :yellow, - my_color: :black - } - - assert Theme.validate(color_map) == {:ok, color_map} - end - - test "validate rejects maps with invalid colors" do - color_map = %{ - text: :red, - background: :green, - border: :invalid, - active: :magenta, - thumb: :cyan, - focus: :yellow, - my_color: :black - } - - {:error, msg} = Theme.validate(color_map) - assert msg =~ "Map entry: :border" - assert msg =~ "Invalid Color specification: :invalid" - end - - test "verify rejects maps without the standard colors" do - color_map = %{some_name: :red} - {:error, msg} = Theme.validate(color_map) - assert msg =~ "didn't include all the required color" - end - - test "verify rejects invalid values" do - {:error, _msg} = Theme.validate("totally wrong") - end -end diff --git a/test/scenic/scene_test.exs b/test/scenic/scene_test.exs index 545a45d3..95f6f458 100644 --- a/test/scenic/scene_test.exs +++ b/test/scenic/scene_test.exs @@ -134,7 +134,7 @@ defmodule Scenic.SceneTest do %ViewPort{} = vp assert is_pid(pid) assert module == TestSceneNoKids - assert theme == :dark + assert theme == {:scenic, :dark} assert is_pid(parent) assert children == nil assert child_supervisor == nil @@ -167,7 +167,7 @@ defmodule Scenic.SceneTest do %ViewPort{} = vp assert is_pid(pid) assert module == TestSceneKids - assert theme == :dark + assert theme == {:scenic, :dark} assert is_pid(parent) %{} = children assert is_pid(child_supervisor) diff --git a/test/scenic/themes_test.exs b/test/scenic/themes_test.exs new file mode 100644 index 00000000..49814bc6 --- /dev/null +++ b/test/scenic/themes_test.exs @@ -0,0 +1,171 @@ +defmodule Scenic.ThemesTest do + use ExUnit.Case, async: true + doctest Scenic.Themes + + alias Scenic.Themes + alias Scenic.Color + + # we expect errors to be logged in this set of tests. This happens when we purposefully + # attempted to load an asset that has been tampered with. So turn off the logging to + # keep the tests clean. + @moduletag :capture_log + + @theme_light %{ + text: :black, + background: :white, + border: :dark_grey, + active: {215, 215, 215}, + thumb: :cornflower_blue, + focus: :blue, + highlight: :saddle_brown + } + + @theme_dark %{ + text: :white, + background: :black, + border: :light_grey, + active: {40, 40, 40}, + thumb: :cornflower_blue, + focus: :cornflower_blue, + highlight: :sandy_brown + } + + @primary Map.merge(@theme_dark, %{background: {72, 122, 252}, active: {58, 94, 201}}) + @secondary Map.merge(@theme_dark, %{background: {111, 117, 125}, active: {86, 90, 95}}) + @success Map.merge(@theme_dark, %{background: {99, 163, 74}, active: {74, 123, 56}}) + @danger Map.merge(@theme_dark, %{background: {191, 72, 71}, active: {164, 54, 51}}) + @warning Map.merge(@theme_light, %{background: {239, 196, 42}, active: {197, 160, 31}}) + @info Map.merge(@theme_dark, %{background: {94, 159, 183}, active: {70, 119, 138}}) + @text Map.merge(@theme_dark, %{text: {72, 122, 252}, background: :clear, active: :clear}) + + @themes %{ + light: @theme_light, + dark: @theme_dark, + primary: @primary, + secondary: @secondary, + success: @success, + danger: @danger, + warning: @warning, + info: @info, + text: @text + } + + @schema [:background, :text, :thumb, :focus, :highlight] + + @properly_configured_module [ + name: :scenic, + themes: @themes, + palette: Scenic.Palette.get() + ] + + # import IEx + + test "module returns the module" do + assert Themes.module() == Scenic.Test.Themes + end + + test "load returns the properly configured themes" do + assert Themes.load() == @properly_configured_module + end + + test "normalize returns the correct theme" do + assert Themes.normalize({:scenic, :dark}) == @theme_dark + end + + test "normalize returns default scenic theme when an atom is passed" do + assert Themes.normalize(:dark) == @theme_dark + end + + test "custom validate method accepts custom named themes" do + assert Themes.validate({:custom_scenic, :custom_dark}) == {:ok, {:custom_scenic, :custom_dark}} + assert Themes.validate({:custom_scenic, :custom_light}) == {:ok, {:custom_scenic, :custom_light}} + assert Themes.validate({:custom_scenic, :custom_primary}) == {:ok, {:custom_scenic, :custom_primary}} + assert Themes.validate({:custom_scenic, :custom_secondary}) == {:ok, {:custom_scenic, :custom_secondary}} + assert Themes.validate({:custom_scenic, :custom_success}) == {:ok, {:custom_scenic, :custom_success}} + assert Themes.validate({:custom_scenic, :custom_danger}) == {:ok, {:custom_scenic, :custom_danger}} + assert Themes.validate({:custom_scenic, :custom_warning}) == {:ok, {:custom_scenic, :custom_warning}} + assert Themes.validate({:custom_scenic, :custom_info}) == {:ok, {:custom_scenic, :custom_info}} + assert Themes.validate({:custom_scenic, :custom_text}) == {:ok, {:custom_scenic, :custom_text}} + end + + test "custom validate method rejects map without custom standard color" do + {:error, msg} = Themes.validate({:custom_scenic, :custom_invalid}) + assert msg =~ "Invalid theme specification" + assert msg =~ "Map entry: :surface" + end + + test "validate accepts the named themes" do + assert Themes.validate({:scenic, :dark}) == {:ok, {:scenic, :dark}} + assert Themes.validate({:scenic, :light}) == {:ok, {:scenic, :light}} + assert Themes.validate({:scenic, :primary}) == {:ok, {:scenic, :primary}} + assert Themes.validate({:scenic, :secondary}) == {:ok, {:scenic, :secondary}} + assert Themes.validate({:scenic, :success}) == {:ok, {:scenic, :success}} + assert Themes.validate({:scenic, :danger}) == {:ok, {:scenic, :danger}} + assert Themes.validate({:scenic, :warning}) == {:ok, {:scenic, :warning}} + assert Themes.validate({:scenic, :info}) == {:ok, {:scenic, :info}} + assert Themes.validate({:scenic, :text}) == {:ok, {:scenic, :text}} + end + + test "validate rejects invalid theme names" do + {:error, msg} = Themes.validate(:invalid) + assert msg =~ "The theme could not be found in library" + end + + test "validate defaults to the scenic library when an atom is passed" do + assert Themes.validate(:primary) == {:ok, :primary} + end + + test "validate accepts maps of colors" do + color_map = %{ + text: :red, + background: :green, + border: :blue, + active: :magenta, + thumb: :cyan, + focus: :yellow, + my_color: :black + } + + assert Themes.validate(color_map) == {:ok, color_map} + end + + test "validate rejects maps with invalid colors" do + color_map = %{ + text: :red, + background: :green, + border: :invalid, + active: :magenta, + thumb: :cyan, + focus: :yellow, + my_color: :black + } + + {:error, msg} = Themes.validate(color_map) + assert msg =~ "Map entry: :border" + assert msg =~ "Invalid Color specification: :invalid" + end + + test "validate accepts a theme against a schema passed in" do + assert Themes.validate({:scenic, :primary}, @schema) + end + + test "validate rejects maps without the standard colors" do + color_map = %{some_name: :red} + {:error, msg} = Themes.validate(color_map) + assert msg =~ "didn't include all the required color" + end + + test "validate rejects invalid values" do + {:error, _msg} = Themes.validate("totally wrong") + end + + @default_schema [:text, :background, :border, :active, :thumb, :focus] + + test "get_schema returns the correct schema" do + assert Themes.get_schema(:scenic) == @default_schema + end + + test "custom color can be retrieved" do + assert Color.to_rgb(:yellow_1) == {:color_rgb, {255, 246, 0}} + end +end diff --git a/test/scenic/view_port_test.exs b/test/scenic/view_port_test.exs index 48ae744d..88390905 100644 --- a/test/scenic/view_port_test.exs +++ b/test/scenic/view_port_test.exs @@ -266,7 +266,7 @@ defmodule Scenic.ViewPortTest do # set the theme - this should restart the current scene test "set_theme works", %{vp: vp} do - assert ViewPort.set_theme(vp, :dark) == :ok + assert ViewPort.set_theme(vp, {:scenic, :dark}) == :ok end # --------------------------------------------------------------------------- diff --git a/test/support/custom_themes.ex b/test/support/custom_themes.ex new file mode 100644 index 00000000..f2c21473 --- /dev/null +++ b/test/support/custom_themes.ex @@ -0,0 +1,63 @@ +defmodule Scenic.Test.CustomThemes do + @theme_light %{ + text: :black, + background: :white, + surface: :gainsboro, + border: :dark_grey, + active: {215, 215, 215}, + thumb: :cornflower_blue, + focus: :blue, + highlight: :saddle_brown + } + + @theme_dark %{ + text: :white, + background: :black, + surface: :gainsboro, + border: :light_grey, + active: {40, 40, 40}, + thumb: :cornflower_blue, + focus: :cornflower_blue, + highlight: :sandy_brown + } + + @theme_dark_invalid %{ + text: :white, + background: :black, + border: :light_grey, + active: {40, 40, 40}, + thumb: :cornflower_blue, + focus: :cornflower_blue + } + + @primary Map.merge(@theme_dark, %{surface: :gainsboro, background: {72, 122, 252}, active: {58, 94, 201}}) + @secondary Map.merge(@theme_dark, %{surface: :gainsboro, background: {111, 117, 125}, active: {86, 90, 95}}) + @success Map.merge(@theme_dark, %{surface: :gainsboro, background: {99, 163, 74}, active: {74, 123, 56}}) + @danger Map.merge(@theme_dark, %{surface: :gainsboro, background: {191, 72, 71}, active: {164, 54, 51}}) + @warning Map.merge(@theme_light, %{surface: :gainsboro, background: {239, 196, 42}, active: {197, 160, 31}}) + @info Map.merge(@theme_dark, %{surface: :gainsboro, background: {94, 159, 183}, active: {70, 119, 138}}) + @text Map.merge(@theme_dark, %{text: {72, 122, 252}, surface: :gainsboro, background: :clear, active: :clear}) + + @themes %{ + custom_light: @theme_light, + custom_dark: @theme_dark, + custom_primary: @primary, + custom_secondary: @secondary, + custom_success: @success, + custom_danger: @danger, + custom_warning: @warning, + custom_info: @info, + custom_text: @text, + custom_invalid: @theme_dark_invalid + } + + @schema [:surface] + + @colors %{ + yellow_1: {0xFF, 0xF6, 0x00} + } + + use Scenic.Themes, [] + + def load(), do: [themes: @themes, schema: @schema, palette: @colors] +end diff --git a/test/support/themes.ex b/test/support/themes.ex new file mode 100644 index 00000000..f004da97 --- /dev/null +++ b/test/support/themes.ex @@ -0,0 +1,46 @@ +defmodule Scenic.Test.Themes do + # @theme_light %{ + # text: :black, + # background: :white, + # border: :dark_grey, + # active: {215, 215, 215}, + # thumb: :cornflower_blue, + # focus: :blue, + # highlight: :saddle_brown + # } + + # @theme_dark %{ + # text: :white, + # background: :black, + # border: :light_grey, + # active: {40, 40, 40}, + # thumb: :cornflower_blue, + # focus: :cornflower_blue, + # highlight: :sandy_brown + # } + + # @primary Map.merge(@theme_dark, %{background: {72, 122, 252}, active: {58, 94, 201}}) + # @secondary Map.merge(@theme_dark, %{background: {111, 117, 125}, active: {86, 90, 95}}) + # @success Map.merge(@theme_dark, %{background: {99, 163, 74}, active: {74, 123, 56}}) + # @danger Map.merge(@theme_dark, %{background: {191, 72, 71}, active: {164, 54, 51}}) + # @warning Map.merge(@theme_light, %{background: {239, 196, 42}, active: {197, 160, 31}}) + # @info Map.merge(@theme_dark, %{background: {94, 159, 183}, active: {70, 119, 138}}) + # @text Map.merge(@theme_dark, %{text: {72, 122, 252}, background: :clear, active: :clear}) + # @themes %{ + # light: @theme_light, + # dark: @theme_dark, + # primary: @primary, + # secondary: @secondary, + # success: @success, + # danger: @danger, + # warning: @warning, + # info: @info, + # text: @text + # } + + use Scenic.Themes, + [ + [name: :scenic, themes: Scenic.Themes], + [name: :custom_scenic, themes: Scenic.Test.CustomThemes] + ] +end diff --git a/test/support/view_port.ex b/test/support/view_port.ex index 8a5ea6a4..fc885535 100644 --- a/test/support/view_port.ex +++ b/test/support/view_port.ex @@ -26,7 +26,7 @@ defmodule Scenic.Test.ViewPort do ) scene_state = %{ - theme: :dark, + theme: {:scenic, :dark}, has_children: true, name: nil, module: __MODULE__, diff --git a/test/test_helper.exs b/test/test_helper.exs index e15d1f04..d557f56c 100644 --- a/test/test_helper.exs +++ b/test/test_helper.exs @@ -1,4 +1,6 @@ # dynamically update the config to point to the test assets Application.put_env(:scenic, :assets, module: Scenic.Test.Assets) +Application.put_env(:scenic, :themes, module: Scenic.Test.Themes) + ExUnit.start()