Packages

Brings CSS-like grid as well as auto-layouts for components. This allows one to dynamically add components to a scene. The goal is to bring some familiar layout apis to Scenic.

Current section

Files

Jump to
scenic_layout_o_matic lib layouts components button.ex
Raw

lib/layouts/components/button.ex

defmodule LayoutOMatic.Button do
# Buttons size based on :button_font_size with 20 being the default; width/height override. Button position is based on the top left point.
@default_font_size 20
@default_font :roboto
@spec translate(%{
component: map,
starting_xy: {number, number},
grid_xy: {number, number},
max_xy: {number, number}
}) ::
{:error, <<_::160, _::_*32>>}
| {:ok, {number, number},
%{
grid_xy: {number, number},
max_xy: number,
primitive: %{data: number, styles: map},
starting_xy: {number, number}
}}
def translate(
%{
component: component,
starting_xy: starting_xy,
grid_xy: grid_xy,
max_xy: max_xy
} = layout
) do
{_, text} = Map.get(component, :data)
styles = button_size(component)
%{font_size: font_size, ascent: ascent, fm_width: fm_width} =
get_font_metrics(text, Map.get(styles, :button_font_size))
height =
case Map.get(styles, :height) do
nil -> font_size + ascent
:auto -> font_size + ascent
height when is_number(height) and height > 0 -> height
end
width =
case Map.get(styles, :width) do
nil -> fm_width + ascent + ascent
:auto -> fm_width + ascent + ascent
width when is_number(width) and width > 0 -> width
end
{starting_x, starting_y} = starting_xy
{grid_x, grid_y} = grid_xy
if starting_xy == grid_xy do
layout = Map.put(layout, :starting_xy, {starting_x + width, starting_y})
# already in a new group, use starting_xy
{:ok, {starting_x, starting_y}, layout}
else
if fits_in_x?(starting_x + width, max_xy) do
# fits in x
# fit in y?
if fits_in_y?(starting_y + height, max_xy) do
# fits
layout = Map.put(layout, :starting_xy, {starting_x + width, starting_y})
{:ok, {starting_x, starting_y}, layout}
# Does not fit
else
{:error, "Does not fit in grid"}
end
else
# fit in new y?
# doesnt fit in x
new_y = grid_y + height
if fits_in_y?(new_y, max_xy) do
# fits in new y, check x
new_layout =
layout
|> Map.put(:grid_xy, {grid_x, new_y})
|> Map.put(:starting_xy, {width, new_y})
{:ok, {grid_x, new_y}, new_layout}
else
{:error, "Does not fit in the grid"}
end
end
end
end
defp button_size(%{styles: %{width: _, height: _, button_font_size: _}} = styles), do: styles
defp button_size(%{styles: %{width: _, height: _}} = styles),
do: Map.put(styles, :button_font_size, @default_font_size)
defp button_size(_), do: Map.new(button_font_size: @default_font_size)
defp get_font_metrics(text, font_size) do
fm = Scenic.Cache.Static.FontMetrics.get!(@default_font)
ascent = FontMetrics.ascent(font_size, fm)
fm_width = FontMetrics.width(text, font_size, fm)
%{font_size: font_size, ascent: ascent, fm_width: fm_width}
end
defp fits_in_x?(potential_x, {max_x, _}),
do: potential_x <= max_x
defp fits_in_y?(potential_y, {_, max_y}),
do: potential_y <= max_y
end