defmodule FloUI.Grid do @moduledoc """ DEPRECATED - use SnapFramework layouts instead. """ # @moduledoc """ # ## Usage in SnapFramework # Render this with children passed to it to automatically lay the children out in the grid. # The children must be given width and height styles for it to work. Inspired by https://github.com/BWheatie/scenic_layout_o_matic # data is a map in the form of ` elixir %{start_xy: {0, 0}, max_xy: {100, 100}}` # ``` elixir # <%= component FloUI.Grid, %{ # start_xy: {0, 0}, # max_xy: {48 * 3, 48} # }, # translate: {20, 120} # do %> # <%= component FloUI.Icon.Button, "Close", id: :icon_button, width: 48, height: 48, translate: {20, 120} do %> # <%= component FloUI.Icon, {:flo_ui, "icons/clear_white.png"} %> # <% end %> # <%= component FloUI.Icon.Button, "Close", id: :icon_button, width: 48, height: 48, translate: {20, 120} do %> # <%= component FloUI.Icon, {:flo_ui, "icons/clear_white.png"} %> # <% end %> # <%= component FloUI.Icon.Button, "Close", id: :icon_button, width: 48, height: 48, translate: {20, 120} do %> # <%= component FloUI.Icon, {:flo_ui, "icons/clear_white.png"} %> # <% end %> # <% end %> # ``` # """ use SnapFramework.Component, name: :grid, template: "lib/layout/grid.eex", controller: :none, assigns: [ last_height: 0, component_xy: {0, 0}, start_xy: {0, 0}, grid_xy: {0, 0}, max_xy: {0, 0} ], opts: [] defcomponent(:grid, :map) def setup(%{assigns: %{data: %{start_xy: start_xy, max_xy: max_xy}} = assigns} = scene) do assigns = %{ assigns | component_xy: start_xy, start_xy: start_xy, grid_xy: start_xy, max_xy: max_xy } {_layout, children} = Enum.reduce(Enum.with_index(assigns.children), {assigns, []}, fn child, acc -> do_layout(child, acc) end) assign(scene, children: children) end def process_info(info, scene) do send_parent(scene, info) {:noreply, scene} end def process_update(data, _opts, scene) do assigns = %{ scene.assigns | component_xy: scene.assigns.start_xy, start_xy: scene.assigns.start_xy, grid_xy: scene.assigns.start_xy, max_xy: scene.assigns.max_xy } {_layout, children} = Enum.reduce(Enum.with_index(assigns.children), {assigns, []}, fn child, acc -> do_layout(child, acc) end) {:noreply, assign(scene, children: children)} end defp do_layout( {[ type: _, module: _, data: _, opts: _ ] = child, i}, {layout, child_list} ) when is_list(child) do case translate(child, layout) do {:error, error} -> {:error, error} new_layout -> translate = new_layout.component_xy updated_child = [ type: child[:type], module: child[:module], data: child[:data], opts: Keyword.put(child[:opts], :translate, translate) ] {new_layout, List.insert_at(child_list, i, updated_child)} # Map.put(new_layout, :children, List.replace_at(new_layout.children, i, updated_child)) end end defp do_layout( {[ type: _, module: _, data: _, opts: _, children: _ ] = child, i}, {layout, child_list} ) when is_list(child) do case translate(child, layout) do {:error, error} -> {:error, error} new_layout -> translate = new_layout.component_xy updated_child = [ type: child[:type], module: child[:module], data: child[:data], opts: Keyword.put(child[:opts], :translate, translate), children: child[:children] ] {new_layout, List.insert_at(child_list, i, updated_child)} # Map.put(new_layout, :children, List.replace_at(new_layout.children, i, updated_child)) end end defp do_layout( {[ type: _, module: _, data: _, children: _, opts: _ ] = child, i}, {layout, child_list} ) when is_list(child) do case translate(child, layout) do {:error, error} -> {:error, error} new_layout -> translate = new_layout.component_xy updated_child = [ type: child[:type], module: child[:module], data: child[:data], children: child[:children], opts: Keyword.put(child[:opts], :translate, translate) ] {new_layout, List.insert_at(child_list, i, updated_child)} # Map.put(new_layout, :children, List.replace_at(new_layout.children, i, updated_child)) end end defp do_layout({child, _i}, {layout, child_list}) when is_list(child) do Enum.reduce(Enum.with_index(child), {layout, child_list}, fn nchild, acc -> do_layout(nchild, acc) end) end defp do_layout(_, layout) do layout end defp translate( child, %{ last_height: last_height, start_xy: start_xy, grid_xy: grid_xy, max_xy: max_xy } = layout ) do width = child[:opts][:width] height = child[:opts][:height] {grid_x, _grid_y} = grid_xy {start_x, start_y} = start_xy new_x = start_x + width case start_xy == max_xy do true -> layout |> Map.put(:start_xy, {start_x, start_y}) |> Map.put(:last_height, height) false -> # already in a new group, use start_xy case fits_in_x?(new_x, max_xy) do # fits in x true -> # fit in y? case fits_in_y?(start_y, max_xy) do true -> # fits layout |> Map.put(:start_xy, {new_x, start_y}) |> Map.put(:component_xy, {start_x, start_y}) |> Map.put(:last_height, height) # Does not fit false -> {:error, "Does not fit in grid"} end # doesnt fit in x false -> # fit in new y? new_y = start_y + last_height case fits_in_y?(new_y, max_xy) do true -> layout |> Map.put(:start_xy, {grid_x + width, new_y}) |> Map.put(:component_xy, {grid_x, new_y}) |> Map.put(:last_height, height) false -> {:error, "Does not fit in the grid"} end end end 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