diff --git a/lib/bds/desktop/shell_live.ex b/lib/bds/desktop/shell_live.ex index ea16502..613fa8c 100644 --- a/lib/bds/desktop/shell_live.ex +++ b/lib/bds/desktop/shell_live.ex @@ -52,6 +52,7 @@ defmodule BDS.Desktop.ShellLive do |> assign(:is_mac_ui, mac_ui?()) |> assign(:menu_groups, titlebar_menu_groups()) |> assign(:titlebar_menu_group, nil) + |> assign(:titlebar_menu_item_index, nil) |> assign(:tab_meta, %{}) |> assign(:project_menu_open, false) |> assign(:sidebar_filters_by_view, %{}) @@ -362,15 +363,22 @@ defmodule BDS.Desktop.ShellLive do {:noreply, handle_native_menu_action(socket, action)} end + def handle_event("titlebar_menu_keydown", %{"key" => key}, socket) do + {:noreply, handle_titlebar_menu_keydown(socket, key)} + end + def handle_event("toggle_titlebar_menu", %{"group" => group}, socket) do - next_group = if socket.assigns.titlebar_menu_group == group, do: nil, else: group - {:noreply, assign(socket, :titlebar_menu_group, next_group)} + {:noreply, + if(socket.assigns.titlebar_menu_group == group, + do: close_titlebar_menu(socket), + else: open_titlebar_menu(socket, group) + )} end def handle_event("hover_titlebar_menu", %{"group" => group}, socket) do socket = if socket.assigns.titlebar_menu_group do - assign(socket, :titlebar_menu_group, group) + open_titlebar_menu(socket, group) else socket end @@ -379,13 +387,13 @@ defmodule BDS.Desktop.ShellLive do end def handle_event("close_titlebar_menu", _params, socket) do - {:noreply, assign(socket, :titlebar_menu_group, nil)} + {:noreply, close_titlebar_menu(socket)} end def handle_event("titlebar_menu_action", %{"action" => action}, socket) do {:noreply, socket - |> assign(:titlebar_menu_group, nil) + |> close_titlebar_menu() |> handle_native_menu_action(action)} end @@ -449,6 +457,7 @@ defmodule BDS.Desktop.ShellLive do |> assign(:panel_tabs, ShellData.panel_tabs(workbench)) |> assign(:supported_ui_languages, ShellData.supported_ui_languages()) |> assign(:menu_groups, socket.assigns[:menu_groups] || titlebar_menu_groups()) + |> assign(:titlebar_menu_item_index, socket.assigns[:titlebar_menu_item_index]) |> assign(:current_tab, current_tab(workbench)) end @@ -1407,10 +1416,149 @@ defmodule BDS.Desktop.ShellLive do DesktopMenuBar.groups(dev_mode?: Application.get_env(:bds, :dev_routes, false)) end + defp titlebar_menu_dropdown_items(group) do + group.items + |> Enum.map_reduce(0, fn item, keyboard_index -> + if Map.get(item, :separator, false) do + {%{separator: true}, keyboard_index} + else + {Map.put(item, :keyboard_index, keyboard_index), keyboard_index + 1} + end + end) + |> elem(0) + end + defp active_titlebar_menu_group(assigns) do Enum.find(assigns.menu_groups || [], fn group -> Atom.to_string(group.id) == assigns.titlebar_menu_group end) end + defp active_titlebar_menu_items(assigns) do + assigns + |> active_titlebar_menu_group() + |> case do + nil -> [] + group -> Enum.reject(group.items, &Map.get(&1, :separator, false)) + end + end + + defp open_titlebar_menu(socket, group) do + socket + |> assign(:titlebar_menu_group, group) + |> assign(:titlebar_menu_item_index, nil) + end + + defp close_titlebar_menu(socket) do + socket + |> assign(:titlebar_menu_group, nil) + |> assign(:titlebar_menu_item_index, nil) + end + + defp handle_titlebar_menu_keydown(socket, key) do + if socket.assigns.titlebar_menu_group do + case key do + "Escape" -> + close_titlebar_menu(socket) + + "ArrowRight" -> + rotate_titlebar_menu_group(socket, 1) + + "ArrowLeft" -> + rotate_titlebar_menu_group(socket, -1) + + "ArrowDown" -> + advance_titlebar_menu_item_index(socket, 1) + + "ArrowUp" -> + advance_titlebar_menu_item_index(socket, -1) + + "Home" -> + set_first_titlebar_menu_item_index(socket) + + "End" -> + set_last_titlebar_menu_item_index(socket) + + "Enter" -> + invoke_active_titlebar_menu_item(socket) + + " " -> + invoke_active_titlebar_menu_item(socket) + + _other -> + socket + end + else + socket + end + end + + defp rotate_titlebar_menu_group(socket, offset) do + groups = socket.assigns.menu_groups || [] + current_group = socket.assigns.titlebar_menu_group + current_index = Enum.find_index(groups, fn group -> Atom.to_string(group.id) == current_group end) + + if is_nil(current_index) or groups == [] do + socket + else + next_index = rem(current_index + offset + length(groups), length(groups)) + next_group = Enum.at(groups, next_index) + open_titlebar_menu(socket, Atom.to_string(next_group.id)) + end + end + + defp advance_titlebar_menu_item_index(socket, offset) do + items = active_titlebar_menu_items(socket.assigns) + current_index = socket.assigns[:titlebar_menu_item_index] + + cond do + items == [] -> + socket + + current_index == nil and offset > 0 -> + assign(socket, :titlebar_menu_item_index, 0) + + current_index == nil and offset < 0 -> + assign(socket, :titlebar_menu_item_index, length(items) - 1) + + true -> + next_index = rem(current_index + offset + length(items), length(items)) + assign(socket, :titlebar_menu_item_index, next_index) + end + end + + defp set_last_titlebar_menu_item_index(socket) do + items = active_titlebar_menu_items(socket.assigns) + + if items == [] do + socket + else + assign(socket, :titlebar_menu_item_index, length(items) - 1) + end + end + + defp set_first_titlebar_menu_item_index(socket) do + items = active_titlebar_menu_items(socket.assigns) + + if items == [] do + socket + else + assign(socket, :titlebar_menu_item_index, 0) + end + end + + defp invoke_active_titlebar_menu_item(socket) do + items = active_titlebar_menu_items(socket.assigns) + + case Enum.at(items, socket.assigns[:titlebar_menu_item_index]) do + %{id: id} -> + socket + |> close_titlebar_menu() + |> handle_native_menu_action(Atom.to_string(id)) + + _other -> + socket + end + end + defp mac_ui? do case Application.get_env(:bds, :shell_platform) do nil -> match?({:unix, :darwin}, :os.type()) diff --git a/lib/bds/desktop/shell_live/index.html.heex b/lib/bds/desktop/shell_live/index.html.heex index c53cbd9..9e680a1 100644 --- a/lib/bds/desktop/shell_live/index.html.heex +++ b/lib/bds/desktop/shell_live/index.html.heex @@ -9,6 +9,7 @@ data-region="title-bar" data-testid="window-titlebar" data-open-menu-group={@titlebar_menu_group || ""} + phx-window-keydown={if(@titlebar_menu_group, do: "titlebar_menu_keydown")} >
<%= for group <- @menu_groups do %> @@ -73,12 +74,15 @@ data-testid="window-titlebar-menu-dropdown" phx-click-away="close_titlebar_menu" > - <%= for item <- group.items do %> + <%= for item <- titlebar_menu_dropdown_items(group) do %> <%= if item.separator do %>
<% else %>