lib/layout/grid.ex

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