Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Theme manager strawman #272

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

crertel
Copy link
Contributor

@crertel crertel commented Jun 20, 2022

Description

Strawman proposal for #267 , as discussed with @boydm .

I'm mostly testing the waters to see if this is heading in the direction we want.

Motivation and Context

See #267 .

Types of changes

This adds a theme manager, which can be used to store various themes for use.

  • Bug fix (a non-breaking change which fixes an issue)
  • [x ] New feature (a non-breaking change which adds functionality)
  • [x ] Breaking change (fix or feature that would cause existing functionality to
    not work as expected)
  • Improvement/refactoring (non-breaking change that doesn't add any feature
    but make things better)

Checklist

  • Check other PRs and make sure that the changes are not done yet.
  • The PR title is no longer than 64 characters.

Comment on lines +7 to +9
@type pallette :: %{
atom() => Scenic.Color.t()
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We assume that a pallette is something like:

%{
primary: Scenic.Color.named().red(),
secondary: Scenic.Color.named().yellow()
}

I wonder if we want to scope-creep this to be any sort of fill or pattern?

Copy link
Collaborator

@axelson axelson Jun 30, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given the types here we can also just use simple atom names for the colors (via the implicit color mechanism) in Scenic.Color

%{
primary: :red,
secondary: :yellow
}

But of course the benefit to the long approach is that you'll get an error earlier if you try to use an invalid color name.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would like the type of a color to be Scenic.Color.t(), which allows for either format of color.

%{
primary: :red,
secondary: :yellow
}

is a very convenient shorthand.
But sometimes you want to be explicit, or add an alpha channel, so that needs to be supported too.

Comment on lines +53 to +56
def put_setting(%__MODULE__{} = theme, :font, font_name), do: %{theme | font: font_name}

def put_setting(%__MODULE__{} = theme, :pallette, pallette) when is_map(pallette),
do: %{theme | pallette: pallette}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not 100% sold on this chaining pattern, but I've seen it other places and it doesn't bother me too much.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 from me to the chaining, I generally like chaining on my structs.

Comment on lines +10 to +11
table_handle =
:ets.new(@ets_name, [:set, :protected, :named_table, {:read_concurrency, true}])
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, we setup a named table here so that anybody can read from it without the full genserver cost.

I'm curious if this function should also setup a default style pulled from either the settings or some normal Scenic default.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree that there should be a default scenic theme. That would avoid user code from having to treat the "no theme" case separately.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Absolutely yes on the default scene. I vote for :dark, but that might be just me...

@crertel
Copy link
Contributor Author

crertel commented Jun 20, 2022

One of the big questions before moving forward is...do we want this thing to have knowledge of an "active" theme? Or should viewports/scenes track that themselves and ask for it before doing drawing?

@@ -0,0 +1,57 @@
defmodule Scenic.Theme do
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm 100% in favor of having separate %Theme{} and theme manager modules. We have a few options for the naming scheme:

  • Theme and ThemeManager
  • Theme and Theme.Manager
  • Themes.Theme and Themes.Manager (i.e. Phoenix context style)

I have an inclination for the first or third but I don't have a super strong feeling.

Copy link
Contributor Author

@crertel crertel Jul 9, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought about this a bit more, and kind of doubling-down on the Phoenix convention, Theme and Manager might be the way to go.

* `pallette` , which takes a `pallette()`.
"""
@spec put_setting(t(), atom, String.t() | pallette()) :: t()
def put_setting(%__MODULE__{} = theme, :font, font_name), do: %{theme | font: font_name}
Copy link
Collaborator

@axelson axelson Jun 30, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We probably want to replace this with a plain put function. And possibly a put_pallette or put_color that would update a color within a pallette, alternatively we could tell users that they need to do:

theme = %Theme{} = get_theme_from_somewhere()
pallette = Map.put(theme.pallete, :secondary, :blue)
Theme.put(theme, :pallete)

* `font` , which takes a `String.t`.
* `pallette` , which takes a `pallette()`.
"""
@spec put_setting(t(), atom, String.t() | pallette()) :: t()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since we're not allowing arbitrary keys we can restrict the type further (although you likely elided this since this is just a strawman). It would look something like:

Suggested change
@spec put_setting(t(), atom, String.t() | pallette()) :: t()
@type setting :: :font | :font_size | :pallette
@spec put_setting(t(), setting(), String.t() | pallette()) :: t()

Comment on lines +29 to +31
@spec create(any) :: {:ok, t()} | {:error, String.t()}
def create(name) when is_atom(name), do: {:ok, %__MODULE__{name: name}}
def create(_), do: {:error, "Themes must be named with an atom."}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Personally since this is just creating a struct instance and not saving it to ETS or a file I would call this function new/1 and new!/ instead of "create". That's just my 2c though.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I've probably been a bit inconsistent here and have used build/n as well.

new/1 and new!/1 feel better

Creates a theme object with a given name.
"""
@spec create(any) :: {:ok, t()} | {:error, String.t()}
def create(name) when is_atom(name), do: {:ok, %__MODULE__{name: name}}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We also discussed having a version of this that would take a bulk update of settings (similar to assigns) so the user doesn't have to repeatedly call put

* `font` is a String of the font that a theme should use, or nil if no preference.
* `pallette` is a map mapping atoms like `:primary` or `:secondary` to a `Scenic.Color`.
"""
@type t :: %__MODULE__{
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure what Scenic's library policy is, but I'm a big fan of https://hex.pm/packages/typed_struct. If we pulled it in, it would make this type much more convenient to write.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it'd be cool to add that all in a big typespec pass later!

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah OK. I hadn't seen typed_struct before. Looks very cool. I'm open to bringing it in.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants