diff --git a/src/actions.rs b/src/actions.rs index 425f32e0f..7a9b245e8 100644 --- a/src/actions.rs +++ b/src/actions.rs @@ -826,7 +826,8 @@ mod tests { let _guard = crate::i18n::lang_test_lock().lock().unwrap_or_else(|e| e.into_inner()); crate::i18n::set_language_code("en-US"); - fn collect(menu: &gpui_widgets::menu::Menu, out: &mut Vec) { + use crate::oakui::component::menu; + fn collect(menu: &menu::Menu, out: &mut Vec) { for item in &menu.items { out.push(item.id); if let Some(sub) = &item.submenu { diff --git a/src/app.rs b/src/app.rs index ce05e734c..68380d0a6 100644 --- a/src/app.rs +++ b/src/app.rs @@ -53,7 +53,8 @@ use gpui::{ use gpui_widgets::audio_meter::{AudioLevelMeter, MeterOrientation}; use gpui_widgets::dialog::progress::{progress_dialog, ProgressContent}; use gpui_widgets::dialog::{DialogButton, Modal, ModalEvent, ModalOptions}; -use gpui_widgets::menu::{Menu, MenuBar, MenuBarEntry, MenuBarEvent, MenuItem}; +use crate::oakui::component::menu::{self, Menu, MenuBar, MenuBarEntry, MenuBarEvent, MenuItem}; +use crate::oakui::component::text_input::install_text_input_bindings; use gpui_widgets::theme::{apply_theme, OakTheme}; use gpui_widgets::viewer::PlaybackClock; @@ -417,6 +418,10 @@ impl OakApp { // the shell's `on_action` listeners — the same path the menu clicks // take through `on_menu`. cx.bind_keys(crate::actions::key_bindings()); + // The text-input editing keys (Backspace/Delete/arrows/Home/End/ + // select-all …) are scoped to the `EditableText` key context; without + // them every field accepts IME text but ignores its editing keys. + install_text_input_bindings(cx); // --- dock ---------------------------------------------------------- let dock = cx.new(|cx| { @@ -763,7 +768,7 @@ impl OakApp { /// through the same path the keyboard shortcuts use, so a menu click /// and a key press can never diverge. fn on_menu(&mut self, item: usize, cx: &mut Context) { - if let Some(index) = crate::menus::shared::language_item_index(item) { + if let Some(index) = menu::language_item_index(item) { let languages = crate::i18n::available_languages(); if let Some(code) = languages.get(index) { let code = code.clone(); @@ -784,11 +789,11 @@ impl OakApp { /// `PanelEvent::Focused`). fn wire_panel_context_menu

(cx: &mut Context, panel: &Entity

, id: PanelId) where - P: gpui::EventEmitter, + P: gpui::EventEmitter, { cx.subscribe( panel, - move |this, _panel, event: &crate::menus::context::ContextMenuTriggered, cx| { + move |this, _panel, event: &menu::ContextMenuTriggered, cx| { this.focused_panel = Some(id); this.on_menu(event.item, cx); }, @@ -2346,10 +2351,10 @@ impl MenuState { } /// One menu item straight from the registry — delegates to -/// [`menus::shared::action_item`](crate::menus::shared::action_item) so the +/// [`menu::action_item`](menu::action_item) so the /// menu bar and the context menus build items the same way. fn menu_item(action: ActionId) -> MenuItem { - crate::menus::shared::action_item(action) + menu::action_item(action) } /// Builds the menu bar entries (文件/编辑/视图/回放/序列/窗口/工具/帮助) from @@ -2478,7 +2483,7 @@ fn make_menus(state: MenuState) -> Vec { tr("menu.view"), Menu::new(vec![ menu_item(A::ThemeDark).with_submenu(theme_submenu), - crate::menus::shared::language_menu(), + menu::language_menu(), menu_item(A::ZoomIn).separated(), menu_item(A::ZoomOut), menu_item(A::IncreaseTrackHeight), @@ -2786,12 +2791,12 @@ mod tests { let zh = submenu .items .iter() - .find(|i| i.id == crate::menus::shared::LANG_ITEM_BASE + zh_index) + .find(|i| i.id == menu::LANG_ITEM_BASE + zh_index) .expect("zh item"); let en = submenu .items .iter() - .find(|i| i.id == crate::menus::shared::LANG_ITEM_BASE + en_index) + .find(|i| i.id == menu::LANG_ITEM_BASE + en_index) .expect("en item"); assert_eq!(zh.label, "简体中文 (zh-CN)"); assert_eq!(en.label, "English (en-US)"); @@ -2803,12 +2808,12 @@ mod tests { let zh = submenu .items .iter() - .find(|i| i.id == crate::menus::shared::LANG_ITEM_BASE + zh_index) + .find(|i| i.id == menu::LANG_ITEM_BASE + zh_index) .expect("zh item"); let en = submenu .items .iter() - .find(|i| i.id == crate::menus::shared::LANG_ITEM_BASE + en_index) + .find(|i| i.id == menu::LANG_ITEM_BASE + en_index) .expect("en item"); assert_eq!(zh.checked, Some(true), "zh-CN is active → checked"); assert_eq!(en.checked, Some(false)); @@ -2826,7 +2831,7 @@ mod tests { let _guard = crate::i18n::lang_test_lock().lock().unwrap_or_else(|e| e.into_inner()); - let dark_item = |dark: bool| -> gpui_widgets::menu::MenuItem { + let dark_item = |dark: bool| -> menu::MenuItem { let entries = make_menus(MenuState::new(dark)); let view = entries .iter() diff --git a/src/dialogs.rs b/src/dialogs.rs index 5d6488ced..c35a886d5 100644 --- a/src/dialogs.rs +++ b/src/dialogs.rs @@ -31,12 +31,13 @@ use gpui::{ div, px, App, Context, ElementId, Entity, EventEmitter, Focusable, FocusHandle, Keystroke, PathPromptOptions, Render, SharedString, Window, }; -use gpui_elements::editable_text::{text_input, EditableTextState, StringStorage, TextChanged}; +use gpui_elements::editable_text::{EditableTextState, StringStorage, TextChanged}; use gpui_widgets::checkbox::{CheckBox, CheckBoxEvent, CheckState}; use gpui_widgets::combo_box::{ComboBox, ComboBoxEvent, ComboBoxOption}; use gpui_widgets::slider::SliderModel; use gpui_widgets::spinbox::{SpinBox, SpinBoxEvent}; use gpui_widgets::value::ValueKind; +use crate::oakui::component::text_input; use crate::actions::ActionId; use crate::i18n; @@ -748,7 +749,7 @@ impl Render for PathField { .px_2() .py_1() .child( - text_input("gpui-widgets-export-path") + text_input("gpui-widgets-export-path", cx) .state(weak) .accepts_input(true), ) @@ -1720,7 +1721,7 @@ impl Render for KeyboardTabContent { .bg(colors.background) .px_2() .py_1() - .child(text_input("keyboard-search-input").state(weak).accepts_input(true)); + .child(text_input("keyboard-search-input", cx).state(weak).accepts_input(true)); // The grouped, filtered action list. let mut list = div() @@ -2221,7 +2222,7 @@ impl Render for ActionSearchContent { .bg(colors.background) .px_2() .py_1() - .child(text_input("action-search-input").state(weak).accepts_input(true)), + .child(text_input("action-search-input", cx).state(weak).accepts_input(true)), ) .child(list) } diff --git a/src/lib.rs b/src/lib.rs index 2fb924005..9b3cd806b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -61,7 +61,6 @@ pub mod app; pub mod dialogs; pub mod i18n; pub mod manager; -pub mod menus; pub mod oakui; pub mod panels; diff --git a/src/manager.rs b/src/manager.rs index f2640dbe5..1c5d82a75 100644 --- a/src/manager.rs +++ b/src/manager.rs @@ -36,7 +36,8 @@ use gpui::{ div, App, ClickEvent, Context, ElementId, Entity, EventEmitter, Hsla, Render, SharedString, Window, }; -use gpui_elements::editable_text::{text_input, EditableTextState, StringStorage}; +use gpui_elements::editable_text::{EditableTextState, StringStorage}; +use crate::oakui::component::text_input; use crate::i18n; use crate::oakui::{AppEngine, LibraryProject}; @@ -438,7 +439,7 @@ impl Render for NamePrompt { .px_2() .py_1() .child( - text_input("gpui-widgets-rename-field") + text_input("gpui-widgets-rename-field", cx) .state(weak) .accepts_input(true), ), diff --git a/src/menus/context.rs b/src/menus/context.rs deleted file mode 100644 index a10fc309e..000000000 --- a/src/menus/context.rs +++ /dev/null @@ -1,81 +0,0 @@ -// Oak Video Editor - Non-Linear Video Editor -// Copyright (C) 2026 Oak Team -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -//! The context-menu plumbing every panel shares: each panel owns one -//! [`ContextMenuHandle`], which wraps the -//! [`ContextMenu`](gpui_widgets::menu::ContextMenu) popup entity and splits -//! its item activations in two — items whose id belongs to the action -//! registry ([`crate::actions::entry_for_menu_id`]) are re-emitted as -//! [`ContextMenuTriggered`] so the app shell routes them through the same -//! dispatch path the menu bar uses, and everything else (the local ids from -//! [`super::shared::LOCAL_ID_BASE`] up) goes to the panel's own handler. -//! This is the Rust counterpart of the C++ panels wiring shared -//! `MenuShared` actions and widget-local slots into one `QMenu`. - -use gpui::{App, AppContext, Context, Entity, EventEmitter, Pixels, Point, Window}; -use gpui_widgets::menu::{ContextMenu, ContextMenuEvent, Menu}; - -/// A registry-backed context-menu item was triggered: the panel re-emits it -/// so the app shell dispatches it like a menu-bar click (after pointing -/// `focused_panel` at the panel, since a right-click does not emit -/// `PanelEvent::Focused`). -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub struct ContextMenuTriggered { - /// The triggered item's id (an action registry menu id). - pub item: usize, -} - -/// A panel's context menu: owns the popup entity, re-emits registry items as -/// [`ContextMenuTriggered`] and hands local items to the panel. -pub struct ContextMenuHandle { - menu: Entity, -} - -impl ContextMenuHandle { - /// Create the popup entity and subscribe to it. `on_local_item` handles - /// every triggered item that is not in the action registry (color - /// labels, panel-specific placeholders, …). - pub fn new(on_local_item: F, window: &mut Window, cx: &mut Context

) -> Self - where - P: EventEmitter, - F: Fn(&mut P, usize, &mut Context

) + 'static, - { - let menu = cx.new(|cx| ContextMenu::new(0, window, cx)); - cx.subscribe( - &menu, - move |panel: &mut P, _menu, event: &ContextMenuEvent, cx| { - if crate::actions::entry_for_menu_id(event.item).is_some() { - cx.emit(ContextMenuTriggered { item: event.item }); - } else { - on_local_item(panel, event.item, cx); - } - }, - ) - .detach(); - Self { menu } - } - - /// Open the menu at `position` (window coordinates). - pub fn show(&self, position: Point, menu: Menu, cx: &mut App) { - self.menu.update(cx, |menu_view, cx| menu_view.show(position, menu, cx)); - } - - /// The popup entity, to be rendered as a child of the panel so the - /// anchored popup can paint above it. - pub fn widget(&self) -> Entity { - self.menu.clone() - } -} diff --git a/src/menus/mod.rs b/src/menus/mod.rs deleted file mode 100644 index dfc975b2b..000000000 --- a/src/menus/mod.rs +++ /dev/null @@ -1,33 +0,0 @@ -// Oak Video Editor - Non-Linear Video Editor -// Copyright (C) 2026 Oak Team -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -//! The right-click menu layer: the Rust counterpart of the C++ context-menu -//! system (`MenuShared` segments + the per-widget `show_context_menu` -//! methods, `app/widget/menu/menushared.cpp` and friends). -//! -//! * [`context`] — the shared plumbing every panel needs to own a -//! [`ContextMenu`](gpui_widgets::menu::ContextMenu): entity creation, -//! event subscription and show/hide, plus the -//! [`ContextMenuTriggered`](context::ContextMenuTriggered) event the -//! panels emit so the shell routes registry items through the same -//! dispatch path the menu bar uses. -//! * [`shared`] — the shared menu segments (edit / clip-edit / in-out / -//! color label / new), built from the action registry -//! ([`crate::actions`]) so ids, labels and shortcut annotations can never -//! diverge from the menu bar. - -pub mod context; -pub mod shared; diff --git a/src/menus/shared.rs b/src/oakui/component/menu.rs similarity index 84% rename from src/menus/shared.rs rename to src/oakui/component/menu.rs index 35eacee64..a71a060c6 100644 --- a/src/menus/shared.rs +++ b/src/oakui/component/menu.rs @@ -14,19 +14,86 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -//! The shared context-menu segments: the Rust counterpart of the C++ -//! `MenuShared` item groups (`add_items_for_edit_menu`, -//! `add_items_for_clip_edit_menu`, `add_items_for_in_out_menu`, -//! `add_items_for_new_menu` and the `ColorLabelMenu`, -//! `app/widget/menu/menushared.cpp` + `app/widget/colorlabelmenu/`). +//! The app's menu component: the menu bar and the context menus. //! -//! Registry-backed items are built from [`crate::actions`] exactly like the -//! menu bar, so ids, labels and shortcut annotations can never diverge. -//! Local (non-registry) items — the 16 color labels today — live at -//! [`LOCAL_ID_BASE`] and above; [`crate::actions::entry_for_menu_id`] is -//! what splits the two worlds at dispatch time. +//! All app menu code lives here instead of reaching into gpui_widgets +//! directly: the rendering-engine types are re-exported +//! ([`Menu`], [`MenuItem`], [`MenuBar`], …), the shared context-menu +//! plumbing ([`ContextMenuHandle`], [`ContextMenuTriggered`]) and the +//! shared menu segments (edit / clip-edit / in-out / color label / new, +//! plus the viewer context menu) are built from the action registry +//! ([`crate::actions`]) exactly like the menu bar, so ids, labels and +//! shortcut annotations can never diverge. +//! +//! Local (non-registry) items — the color labels and the dynamic +//! language items — live at [`LOCAL_ID_BASE`] and above; +//! [`crate::actions::entry_for_menu_id`] splits the two worlds at +//! dispatch time. -use gpui_widgets::menu::{Menu, MenuItem}; +pub use gpui_widgets::menu::{ + ContextMenu, ContextMenuEvent, Menu, MenuBar, MenuBarEntry, MenuBarEvent, MenuItem, +}; + +// --------------------------------------------------------------------------- +// Context-menu plumbing +// --------------------------------------------------------------------------- + +use gpui::{App, AppContext, Context, Entity, EventEmitter, Pixels, Point, Window}; +/// A registry-backed context-menu item was triggered: the panel re-emits it +/// so the app shell dispatches it like a menu-bar click (after pointing +/// `focused_panel` at the panel, since a right-click does not emit +/// `PanelEvent::Focused`). +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct ContextMenuTriggered { + /// The triggered item's id (an action registry menu id). + pub item: usize, +} + +/// A panel's context menu: owns the popup entity, re-emits registry items as +/// [`ContextMenuTriggered`] and hands local items to the panel. +pub struct ContextMenuHandle { + menu: Entity, +} + +impl ContextMenuHandle { + /// Create the popup entity and subscribe to it. `on_local_item` handles + /// every triggered item that is not in the action registry (color + /// labels, panel-specific placeholders, …). + pub fn new(on_local_item: F, window: &mut Window, cx: &mut Context

) -> Self + where + P: EventEmitter, + F: Fn(&mut P, usize, &mut Context

) + 'static, + { + let menu = cx.new(|cx| ContextMenu::new(0, window, cx)); + cx.subscribe( + &menu, + move |panel: &mut P, _menu, event: &ContextMenuEvent, cx| { + if crate::actions::entry_for_menu_id(event.item).is_some() { + cx.emit(ContextMenuTriggered { item: event.item }); + } else { + on_local_item(panel, event.item, cx); + } + }, + ) + .detach(); + Self { menu } + } + + /// Open the menu at `position` (window coordinates). + pub fn show(&self, position: Point, menu: Menu, cx: &mut App) { + self.menu.update(cx, |menu_view, cx| menu_view.show(position, menu, cx)); + } + + /// The popup entity, to be rendered as a child of the panel so the + /// anchored popup can paint above it. + pub fn widget(&self) -> Entity { + self.menu.clone() + } +} + +// --------------------------------------------------------------------------- +// Shared menu segments +// --------------------------------------------------------------------------- use crate::actions::ActionId; diff --git a/src/oakui/component/mod.rs b/src/oakui/component/mod.rs new file mode 100644 index 000000000..0c900cd25 --- /dev/null +++ b/src/oakui/component/mod.rs @@ -0,0 +1,41 @@ +// Oak Video Editor - Non-Linear Video Editor +// Copyright (C) 2026 Oak Team +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +//! The app's reusable UI components — the presentation layer above raw +//! gpui. Components own their theme wiring (colors come from the app +//! theme, not hard-coded defaults), their key bindings and their +//! interaction rules, so call sites stay declarative: +//! +//! ``` +//! use crate::oakui::component::{text_input, TextInput}; +//! +//! // in a render(): +//! child( +//! text_input("my-field", window, cx) +//! .state(self.value.downgrade()) +//! .accepts_input(true), +//! ) +//! ``` +//! +//! Submodules: +//! - [`text_input`]: the text field (IME, caret/selection, editing keys). +//! - [`menu`]: menus (menu bar entries and context menus). + +pub mod menu; +pub mod text_input; + +pub use menu::{ContextMenu, Menu, MenuItem, MenuBar}; +pub use text_input::{install_text_input_bindings, text_input, TextInput}; diff --git a/src/oakui/component/text_input.rs b/src/oakui/component/text_input.rs new file mode 100644 index 000000000..cadbff575 --- /dev/null +++ b/src/oakui/component/text_input.rs @@ -0,0 +1,141 @@ +// Oak Video Editor - Non-Linear Video Editor +// Copyright (C) 2026 Oak Team +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +//! The app's text input component. +//! +//! Wraps the gpui_elements editing engine (IME composition, caret, +//! selection, undo history) with the app's own presentation rules: +//! +//! - **Theme colors**: text, caret, selection, placeholder and the IME +//! marked underline all come from `App::default_colors` (the app theme) +//! instead of the engine's hard-coded defaults. The field's text color +//! is applied through a wrapping `div().text_color(…)` refinement so +//! the engine's run layout (which reads `Window::text_style`) picks it +//! up. +//! - **Editing key bindings**: [`install_text_input_bindings`] installs +//! the engine's Backspace/Delete/arrow/Home/End/select-all bindings +//! into the app keymap, scoped to the `EditableText` key context. The +//! app previously never installed them, so every field accepted IME +//! text but every editing key was a no-op. +//! +//! IME text insertion itself flows through the engine's +//! `EntityInputHandler` (registered by the element via +//! `Window::handle_input`), so composing text, marked ranges and the +//! candidate-window position all work once the field is focused. + +use std::sync::Arc; + +use gpui::{ + colors::{Colors, DefaultColors}, + div, App, Div, ElementId, Hsla, IntoElement, ParentElement, Styled, WeakEntity, Window, +}; +use gpui_elements::editable_text::actions::{default_bindings, DEFAULT_INPUT_CONTEXT}; +use gpui_elements::editable_text::{EditableTextElement, EditableTextState}; + +/// Install the text-input editing key bindings into the app keymap. +/// +/// The editing keys (Backspace, Delete, arrows, Home/End, select-all, +/// …) are gpui actions bound to the `EditableText` key context; without +/// them the fields accept IME text but every editing key is a no-op. +/// Call once at app startup, alongside the registry key bindings. +pub fn install_text_input_bindings(cx: &mut App) { + cx.bind_keys(default_bindings().as_keybindings(Some(DEFAULT_INPUT_CONTEXT))); +} + +/// The app's text input component. +/// +/// A `div` that carries the theme text color refinement around the +/// gpui_elements editing element, with the theme's selection / caret / +/// placeholder / IME-marked colors applied directly. Build with +/// [`text_input`], then chain the engine's element options (`.state`, +/// `.accepts_input`, …). +pub struct TextInput { + element: EditableTextElement, + text_color: Hsla, + selection: Hsla, + caret: Hsla, + placeholder: Hsla, +} + +impl TextInput { + /// Bind the field to an existing editing state entity (the caller + /// reads the value / subscribes to changes through it). + pub fn state(mut self, state: WeakEntity) -> Self { + self.element = self.element.state(state); + self + } + + /// Whether the field accepts input at all (disabled fields render + /// without the input handler attached). + pub fn accepts_input(mut self, enabled: bool) -> Self { + self.element = self.element.accepts_input(enabled); + self + } + + /// Placeholder shown while the field is empty. + pub fn placeholder(mut self, text: impl Into) -> Self { + self.element = self.element.placeholder(text); + self + } +} + +impl IntoElement for TextInput { + type Element = Div; + + fn into_element(self) -> Div { + let Self { + element, + text_color, + selection, + caret, + placeholder, + } = self; + div() + // The text_color refinement is pushed onto the window text + // style during paint, so the engine's run layout (which reads + // `Window::text_style().color`) renders in the theme text + // color — the field reads like every other label in the app. + .text_color(text_color) + .child( + element + .placeholder_color(placeholder) + .selection_color(selection) + .caret_color(caret) + .marked_color(text_color), + ) + } +} + +/// Build the app's text input with the current theme's colors. +/// +/// `cx` is used for the theme colors; the returned component needs no +/// further styling to match the rest of the UI. +pub fn text_input(id: impl Into, cx: &App) -> TextInput { + let colors = theme_colors(cx); + TextInput { + element: gpui_elements::editable_text::text_input(id), + text_color: colors.text.into(), + selection: colors.selected.into(), + caret: colors.text.into(), + placeholder: colors.disabled.into(), + } +} + +/// The theme colors for the app (falls back to the defaults when no +/// theme has been applied yet). +fn theme_colors(cx: &App) -> Arc { + cx.default_colors().clone() +} diff --git a/src/oakui/mod.rs b/src/oakui/mod.rs index 90a34c252..733bb2b1b 100644 --- a/src/oakui/mod.rs +++ b/src/oakui/mod.rs @@ -40,6 +40,7 @@ //! * [`timecode`] — timecode / duration / fps / resolution formatting (pure, //! unit tested). +pub mod component; pub mod displaycolor; pub mod effectchain; pub mod engine; diff --git a/src/panels/effect_library.rs b/src/panels/effect_library.rs index 6ce9a227e..af8c15265 100644 --- a/src/panels/effect_library.rs +++ b/src/panels/effect_library.rs @@ -26,7 +26,8 @@ use gpui::{ div, prelude::*, AnyElement, App, ClickEvent, Context, Entity, EventEmitter, MouseButton, Render, SharedString, Window, }; -use gpui_elements::editable_text::{text_input, EditableTextState, StringStorage, TextChanged}; +use gpui_elements::editable_text::{EditableTextState, StringStorage, TextChanged}; +use crate::oakui::component::text_input; use crate::i18n; use crate::oakui::AppEngine; @@ -162,7 +163,7 @@ impl Render for EffectLibraryPanel { .border_b_1() .border_color(colors.border) .child( - text_input("effect-library-search") + text_input("effect-library-search", cx) .state(self.search.downgrade()) .accepts_input(true), ), diff --git a/src/panels/history.rs b/src/panels/history.rs index d51929e8e..a73d7108b 100644 --- a/src/panels/history.rs +++ b/src/panels/history.rs @@ -27,7 +27,7 @@ use gpui::{ div, prelude::*, px, AnyElement, App, Context, ElementId, Entity, EventEmitter, MouseButton, MouseDownEvent, Render, SharedString, Window, }; -use gpui_widgets::menu::{ContextMenu, ContextMenuEvent, Menu, MenuItem}; +use crate::oakui::component::menu::{ContextMenu, ContextMenuEvent, Menu, MenuItem}; use crate::oakui::AppEngine; use crate::panels::commands::PanelCommandHandler; diff --git a/src/panels/inspector.rs b/src/panels/inspector.rs index a9dbcfd93..1931f3e19 100644 --- a/src/panels/inspector.rs +++ b/src/panels/inspector.rs @@ -26,9 +26,9 @@ use gpui::{ div, prelude::*, AnyElement, App, Context, Entity, EventEmitter, MouseButton, Render, SharedString, Window, }; -use gpui_widgets::menu::{Menu, MenuItem}; +use crate::oakui::component::menu::{Menu, MenuItem}; -use crate::menus::context::{ContextMenuHandle, ContextMenuTriggered}; +use crate::oakui::component::menu::{ContextMenuHandle, ContextMenuTriggered}; use crate::oakui::AppEngine; use crate::panels::commands::PanelCommandHandler; use crate::panels::ids::INSPECTOR; diff --git a/src/panels/node_editor.rs b/src/panels/node_editor.rs index aa6f98a26..44e270517 100644 --- a/src/panels/node_editor.rs +++ b/src/panels/node_editor.rs @@ -37,10 +37,10 @@ use gpui::{ div, point, prelude::*, px, AnyElement, App, Bounds, ClickEvent, Context, Entity, EventEmitter, MouseButton, Pixels, Point, Render, SharedString, Window, }; -use gpui_widgets::menu::{Menu, MenuItem}; +use crate::oakui::component::menu::{Menu, MenuItem}; -use crate::menus::context::{ContextMenuHandle, ContextMenuTriggered}; -use crate::menus::shared; +use crate::oakui::component::menu::{ContextMenuHandle, ContextMenuTriggered}; +use crate::oakui::component::menu; use crate::oakui::{AppEngine, NodeLibraryEntry}; use crate::panels::commands::PanelCommandHandler; use crate::panels::ids::NODE_EDITOR; @@ -156,7 +156,7 @@ impl NodeEditorPanel { /// Handles the node editor's local (non-registry) context-menu items. fn on_local_menu_item(&mut self, item: usize, cx: &mut Context) { - if let Some(color) = shared::color_label_index(item) { + if let Some(color) = menu::color_label_index(item) { println!("[node editor] set node color label to {color}"); return; } @@ -442,13 +442,13 @@ const LOCAL_ADD_NODE_BASE: usize = 2420; /// viewer/parameter-editor reveals and properties (the C++ node branch). pub(crate) fn node_menu() -> Menu { use crate::i18n::tr; - let mut items = shared::edit_section(false); + let mut items = menu::edit_section(false); if let Some(last) = items.last_mut() { last.separator_after = true; } items.push(MenuItem::new(LOCAL_GROUP, tr("node.context.group"))); items.push(MenuItem::new(LOCAL_UNGROUP, tr("node.context.ungroup"))); - items.push(shared::color_label_item(None).separated()); + items.push(menu::color_label_item(None).separated()); items.push(MenuItem::new(LOCAL_OPEN_IN_VIEWER, tr("node.context.open_in_viewer"))); items.push(MenuItem::new( LOCAL_SHOW_IN_PARAM_EDITOR, diff --git a/src/panels/ofx_params.rs b/src/panels/ofx_params.rs index 4ed882bc3..8247f7a5f 100644 --- a/src/panels/ofx_params.rs +++ b/src/panels/ofx_params.rs @@ -42,6 +42,7 @@ //! the widget values from the engine snapshot on every render. use std::sync::Arc; +use crate::oakui::component::text_input; use gpui::effect_stack::EffectId; use gpui::colors::DefaultColors; @@ -53,7 +54,7 @@ use gpui::{ Anchor, App, Bounds, ElementId, Hsla, KeyDownEvent, MouseButton, MouseDownEvent, MouseUpEvent, Point, Pixels, Rgba, anchored, canvas, deferred, fill, }; -use gpui_elements::editable_text::{text_input, EditableTextState, StringStorage}; +use gpui_elements::editable_text::{EditableTextState, StringStorage}; use gpui_widgets::checkbox::{CheckBox, CheckBoxEvent, CheckState}; use gpui_widgets::combo_box::{ComboBox, ComboBoxEvent, ComboBoxOption}; use gpui_widgets::slider::{Slider, SliderEvent, SliderModel}; @@ -912,7 +913,7 @@ impl Render for OfxParamsView { .bg(colors.background) .px_2() .py_1() - .child(text_input(format!("ofx-param-{}", control.input_id)).state(weak).accepts_input(true)), + .child(text_input(format!("ofx-param-{}", control.input_id), cx).state(weak).accepts_input(true)), ) .child( // Explicit commit: reads the field and pushes the @@ -1267,7 +1268,7 @@ impl OfxColorPicker { .px_2() .py_1() .child( - text_input(format!("ofx-color-hex-{control}")) + text_input(format!("ofx-color-hex-{control}"), cx) .state(hex_weak) .accepts_input(true), ), diff --git a/src/panels/program_viewer.rs b/src/panels/program_viewer.rs index 23a2ca532..54d3deda6 100644 --- a/src/panels/program_viewer.rs +++ b/src/panels/program_viewer.rs @@ -31,7 +31,8 @@ use gpui_widgets::scopes::{ChromaDataSource, Histogram, LumaDataSource, Vectorsc use gpui_widgets::viewer::{InteractPointerKind, PlaybackClock, ViewerEvent, ViewerWidget}; use crate::actions::ActionId; -use crate::menus::context::{ContextMenuHandle, ContextMenuTriggered}; +use crate::oakui::component::menu; +use crate::oakui::component::menu::{ContextMenuHandle, ContextMenuTriggered}; use crate::oakui::ofx::InteractViewport; use crate::oakui::timecode::{format_fps, format_resolution}; use crate::oakui::{AppEngine, Monitor}; @@ -206,7 +207,7 @@ impl ProgramViewerPanel { /// Handles the viewer's local (non-registry) context-menu items. fn on_local_menu_item(&mut self, item: usize, cx: &mut Context) { - use crate::menus::shared as shared_menu; + use crate::oakui::component::menu as shared_menu; let divider = match item { shared_menu::LOCAL_VIEWER_RES_FULL => Some(1), shared_menu::LOCAL_VIEWER_RES_HALF => Some(2), @@ -550,7 +551,7 @@ impl Render for ProgramViewerPanel { let divider = this.engine.read(cx).playback_divider(); this.context_menu.show( event.position, - crate::menus::shared::viewer_menu(divider), + menu::viewer_menu(divider), cx, ); }) diff --git a/src/panels/project_explorer.rs b/src/panels/project_explorer.rs index 6f0fa3a7e..5d0cd4ccd 100644 --- a/src/panels/project_explorer.rs +++ b/src/panels/project_explorer.rs @@ -25,12 +25,12 @@ use gpui::{ div, px, prelude::*, AnyElement, App, Context, Entity, EventEmitter, MouseButton, PathPromptOptions, Pixels, Point, Render, SharedString, Window, }; -use gpui_widgets::menu::{Menu, MenuItem}; +use crate::oakui::component::menu::{Menu, MenuItem}; use gpui_widgets::project_explorer::{ProjectExplorer, ProjectExplorerEvent}; use crate::actions::ActionId; -use crate::menus::context::{ContextMenuHandle, ContextMenuTriggered}; -use crate::menus::shared; +use crate::oakui::component::menu::{ContextMenuHandle, ContextMenuTriggered}; +use crate::oakui::component::menu; use crate::oakui::AppEngine; use crate::panels::commands::PanelCommandHandler; use crate::panels::ids::PROJECT; @@ -311,7 +311,7 @@ fn proxy_submenu(row: Option<&crate::oakui::engine::ProxyFootageRow>) -> Menu { use_proxy, reveal, delete, - shared::action_item(ActionId::ProxySettings).separated(), + menu::action_item(ActionId::ProxySettings).separated(), ]) } @@ -319,8 +319,8 @@ fn proxy_submenu(row: Option<&crate::oakui::engine::ProxyFootageRow>) -> Menu { pub(crate) fn blank_menu() -> Menu { Menu::new(vec![ MenuItem::new(0, crate::i18n::tr("project.context.new")) - .with_submenu(Menu::new(shared::new_section())), - shared::action_item(ActionId::Import), + .with_submenu(Menu::new(menu::new_section())), + menu::action_item(ActionId::Import), ]) } diff --git a/src/panels/source_viewer.rs b/src/panels/source_viewer.rs index 0f7f19aa2..c01c049d2 100644 --- a/src/panels/source_viewer.rs +++ b/src/panels/source_viewer.rs @@ -26,7 +26,8 @@ use gpui::{ use gpui_widgets::viewer::{ViewerEvent, ViewerWidget}; use crate::actions::ActionId; -use crate::menus::context::{ContextMenuHandle, ContextMenuTriggered}; +use crate::oakui::component::menu; +use crate::oakui::component::menu::{ContextMenuHandle, ContextMenuTriggered}; use crate::oakui::timecode::{format_fps, format_resolution}; use crate::oakui::{AppEngine, Monitor}; use crate::panels::commands::{self as panel_commands, PanelCommandHandler}; @@ -86,7 +87,7 @@ impl SourceViewerPanel { /// Handles the viewer's local (non-registry) context-menu items. fn on_local_menu_item(&mut self, item: usize, cx: &mut Context) { - use crate::menus::shared as shared_menu; + use crate::oakui::component::menu as shared_menu; let divider = match item { shared_menu::LOCAL_VIEWER_RES_FULL => Some(1), shared_menu::LOCAL_VIEWER_RES_HALF => Some(2), @@ -161,7 +162,7 @@ impl Render for SourceViewerPanel { let divider = this.engine.read(cx).playback_divider(); this.context_menu.show( event.position, - crate::menus::shared::viewer_menu(divider), + menu::viewer_menu(divider), cx, ); }) diff --git a/src/panels/timeline.rs b/src/panels/timeline.rs index e674d5592..ecc6858b0 100644 --- a/src/panels/timeline.rs +++ b/src/panels/timeline.rs @@ -50,7 +50,7 @@ use gpui::{ }; use gpui::{AnyElement, App, ClickEvent, DragMoveEvent, EventEmitter, Render, SharedString}; use gpui_widgets::checkbox::{CheckBox, CheckBoxEvent, CheckState}; -use gpui_widgets::menu::{Menu, MenuItem}; +use crate::oakui::component::menu::{Menu, MenuItem}; use gpui_widgets::viewer::PlaybackClock; use gpui_widgets::project_explorer::FootageDrag; use gpui_widgets::slider::{Slider, SliderEvent, SliderModel}; @@ -59,8 +59,8 @@ use gpui_widgets::value::ValueKind; use crate::actions::ActionId; use crate::i18n; -use crate::menus::context::{ContextMenuHandle, ContextMenuTriggered}; -use crate::menus::shared; +use crate::oakui::component::menu::{ContextMenuHandle, ContextMenuTriggered}; +use crate::oakui::component::menu; use crate::oakui::icons; use crate::oakui::{AppEngine, Monitor}; use crate::panels::commands::{self as panel_commands, PanelCommandHandler}; @@ -269,7 +269,7 @@ impl TimelinePanel { // Color labels apply to the selected clips; the engine has no // clip-color surface yet, so they log for now (kept visible so the // wiring is testable in the demo). - if let Some(color) = shared::color_label_index(item) { + if let Some(color) = menu::color_label_index(item) { println!("[timeline] set clip color label to {color}"); return; } @@ -1078,29 +1078,29 @@ pub(crate) fn clip_menu( proxy: &[crate::oakui::engine::ProxyFootageRow], multicam: Option, ) -> Menu { - let mut items = shared::edit_section(true); + let mut items = menu::edit_section(true); // The C++ puts a separator between the edit section and the color // labels, and another after them. if let Some(last) = items.last_mut() { last.separator_after = true; } - items.push(shared::color_label_item(None).separated()); + items.push(menu::color_label_item(None).separated()); // Synchronize group (registry actions; enabled at ≥ 2 eligible clips, // the C++ `get_selected_source_sync_clips` / `_waveform_sync_clips` // counts). let sync_enabled = sync.source_time >= 2; let wave_enabled = sync.waveform >= 2; - let mut source_time = shared::action_item(ActionId::SyncBySourceTime); + let mut source_time = menu::action_item(ActionId::SyncBySourceTime); if !sync_enabled { source_time = source_time.disabled(); } items.push(source_time); - let mut waveform = shared::action_item(ActionId::SyncByWaveform); + let mut waveform = menu::action_item(ActionId::SyncByWaveform); if !wave_enabled { waveform = waveform.disabled(); } items.push(waveform); - let mut waveform_speed = shared::action_item(ActionId::SyncByWaveformSpeed).separated(); + let mut waveform_speed = menu::action_item(ActionId::SyncByWaveformSpeed).separated(); if !wave_enabled { waveform_speed = waveform_speed.disabled(); } @@ -1144,7 +1144,7 @@ pub(crate) fn clip_menu( use_proxy, reveal, delete, - shared::action_item(ActionId::ProxySettings).separated(), + menu::action_item(ActionId::ProxySettings).separated(), ]); items.push(MenuItem::new(0, i18n::tr("timeline.context.proxy")).with_submenu(proxy_menu)); // Reveal / multi-cam entries (the C++ shows them only when the clip is @@ -1218,8 +1218,8 @@ pub(crate) fn track_head_menu() -> Menu { /// The marker context menu (`SeekableWidget`): color labels, the plain /// edit section and marker properties. pub(crate) fn marker_menu() -> Menu { - let mut items = vec![shared::color_label_item(None).separated()]; - let mut edit_items = shared::edit_section(false); + let mut items = vec![menu::color_label_item(None).separated()]; + let mut edit_items = menu::edit_section(false); // Separator before the trailing "Properties" entry (the C++ layout). if let Some(last) = edit_items.last_mut() { last.separator_after = true; @@ -1418,7 +1418,7 @@ mod tests { .expect("color label item"); assert_eq!( color.submenu.as_ref().unwrap().items.len(), - shared::COLOR_LABEL_COUNT + menu::COLOR_LABEL_COUNT ); // The synchronize entries are the registry actions and stay