Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
140 changes: 140 additions & 0 deletions crates/openlogi-desktop/src/features/mouse/inspector.rs
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,7 @@ fn button_inspector(
"inspector-action",
Some(&action),
picker.search,
picker.view,
&on_pick,
pal,
cx,
Expand Down Expand Up @@ -285,6 +286,7 @@ fn inherited_gesture_inspector(
"inspector-gesture-override",
None,
picker.search,
picker.view,
&on_pick,
pal,
cx,
Expand Down Expand Up @@ -348,6 +350,7 @@ fn gesture_inspector(
"inspector-gesture-action",
Some(&current),
picker.search,
picker.view,
&on_pick,
pal,
cx,
Expand Down Expand Up @@ -653,6 +656,9 @@ fn selection_card(
search.update(cx, |search, cx| search.set_value("", window, cx));
}
toggle.update(cx, |view, cx| {
if opening {
view.clear_custom_action_drafts(window, cx);
}
view.toggle_action_picker();
cx.notify();
});
Expand All @@ -663,15 +669,41 @@ fn action_library(
id_prefix: &'static str,
current: Option<&Action>,
action_search: &Entity<InputState>,
view: &Entity<MouseModelView>,
on_pick: &PickFn,
pal: Palette,
cx: &Context<MouseModelView>,
) -> impl IntoElement {
let query = action_search.read(cx).value();
let rows = action_rows_matching(id_prefix, current, &query, on_pick, pal);
let (shortcut_input, application_input, shortcut_invalid, application_invalid) = {
let view_ref = view.read(cx);
(
view_ref.custom_shortcut_input.clone(),
view_ref.custom_application_input.clone(),
view_ref.custom_shortcut_invalid,
view_ref.custom_application_invalid,
)
};
v_flex()
.gap_2()
.pt_1()
.child(custom_shortcut_editor(
id_prefix,
&shortcut_input,
shortcut_invalid,
view,
on_pick,
pal,
))
.child(custom_application_editor(
id_prefix,
&application_input,
application_invalid,
view,
on_pick,
pal,
))
.child(editor_section(tr!("actions.actions"), pal))
.child(control_input(action_search).cleanable(true))
.child(
Expand All @@ -690,6 +722,114 @@ fn action_library(
)
}

/// A single-field "Custom Shortcut" editor, matching the Action Ring editor's
/// `shortcut_editor` (`features/action_ring/editor.rs`) so the same custom
/// action is reachable from the plain per-button picker, not just the ring.
fn custom_shortcut_editor(
id_prefix: &'static str,
input: &Entity<InputState>,
invalid: bool,
view: &Entity<MouseModelView>,
on_pick: &PickFn,
pal: Palette,
) -> impl IntoElement {
let submit_input = input.clone();
let on_pick = on_pick.clone();
let view = view.clone();
v_flex()
.gap_1()
.child(editor_section(tr!("action_ring.custom_shortcut"), pal))
.child(
h_flex()
.gap_2()
.child(
div()
.flex_1()
.min_w_0()
.child(control_input(input).cleanable(true)),
)
.child(
Button::new(format!("{id_prefix}-custom-shortcut-add"))
.compact()
.label(tr!("common.add"))
.on_click(move |_, window, cx| {
let shortcut = submit_input.read(cx).value().to_string();
match shortcut.parse::<openlogi_core::binding::KeyCombo>() {
Ok(combo) => (on_pick)(Action::CustomShortcut(combo), window, cx),
Err(_) => view.update(cx, |view, cx| {
view.custom_shortcut_invalid = true;
cx.notify();
}),
}
Comment thread
greptile-apps[bot] marked this conversation as resolved.
}),
),
)
.when(invalid, |editor| {
editor.child(
div()
.text_caption()
.text_color(rgb(0x00ef_4444))
.child(tr!("action_ring.custom_action_invalid_input")),
)
})
}

/// A single-field "Open Application or Folder" editor, matching the Action
/// Ring editor's `path_editor`.
fn custom_application_editor(
id_prefix: &'static str,
input: &Entity<InputState>,
invalid: bool,
view: &Entity<MouseModelView>,
on_pick: &PickFn,
pal: Palette,
) -> impl IntoElement {
let submit_input = input.clone();
let on_pick = on_pick.clone();
let view = view.clone();
v_flex()
.gap_1()
.child(editor_section(
tr!("action_ring.open_application_or_folder"),
pal,
))
.child(
h_flex()
.gap_2()
.child(
div()
.flex_1()
.min_w_0()
.child(control_input(input).cleanable(true)),
)
.child(
Button::new(format!("{id_prefix}-custom-application-add"))
.compact()
.label(tr!("common.add"))
.on_click(move |_, window, cx| {
let path = submit_input.read(cx).value().to_string();
match openlogi_core::binding::ApplicationTarget::new(path, "") {
Ok(target) => {
(on_pick)(Action::OpenApplication(target), window, cx);
}
Err(_) => view.update(cx, |view, cx| {
view.custom_application_invalid = true;
cx.notify();
}),
}
}),
),
)
.when(invalid, |editor| {
editor.child(
div()
.text_caption()
.text_color(rgb(0x00ef_4444))
.child(tr!("action_ring.custom_action_invalid_input")),
)
})
}

fn gesture_action(
gesture_map: &BTreeMap<GestureDirection, Action>,
button: ButtonId,
Expand Down
106 changes: 104 additions & 2 deletions crates/openlogi-desktop/src/features/mouse/view.rs
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,12 @@ pub struct MouseModelView {
gesture_active_dir: Option<GestureDirection>,
action_picker_open: bool,
action_search: Entity<InputState>,
pub(super) custom_shortcut_input: Entity<InputState>,
pub(super) custom_application_input: Entity<InputState>,
/// Whether the last "Add" attempt on the corresponding custom editor
/// failed to parse, so its caption can show an inline error.
pub(super) custom_shortcut_invalid: bool,
pub(super) custom_application_invalid: bool,
_state_obs: Subscription,
}

Expand All @@ -146,6 +152,31 @@ impl MouseModelView {
}
})
.detach();
let custom_shortcut_input = cx.new(|cx| {
InputState::new(window, cx)
.placeholder(tr!("action_ring.shortcut_e_g_cmd_plus_shift_plus_p"))
});
cx.subscribe(&custom_shortcut_input, |view, _, event: &InputEvent, cx| {
if matches!(event, InputEvent::Change) {
view.custom_shortcut_invalid = false;
cx.notify();
}
})
.detach();
let custom_application_input = cx.new(|cx| {
InputState::new(window, cx)
.placeholder(tr!("action_ring.application_folder_path_or_url"))
});
Comment thread
greptile-apps[bot] marked this conversation as resolved.
cx.subscribe(
&custom_application_input,
|view, _, event: &InputEvent, cx| {
if matches!(event, InputEvent::Change) {
view.custom_application_invalid = false;
cx.notify();
}
},
)
.detach();
let state = AppState::global(cx);
let state_obs = cx.subscribe(&state, |_view, _, event: &StateEvent, cx| {
let relevant = match event {
Expand All @@ -171,10 +202,30 @@ impl MouseModelView {
gesture_active_dir: None,
action_picker_open: false,
action_search,
custom_shortcut_input,
custom_application_input,
custom_shortcut_invalid: false,
custom_application_invalid: false,
_state_obs: state_obs,
}
}

/// Clear both custom-action drafts (text and any invalid state) — called
/// whenever the picker opens for a new target, so a shortcut or
/// application typed for one button doesn't reappear for another.
pub(super) fn clear_custom_action_drafts(
&mut self,
window: &mut Window,
cx: &mut Context<Self>,
) {
self.custom_shortcut_input
.update(cx, |input, cx| input.set_value("", window, cx));
self.custom_application_input
.update(cx, |input, cx| input.set_value("", window, cx));
self.custom_shortcut_invalid = false;
self.custom_application_invalid = false;
}

/// Set (or clear, with `None`) the activated gesture direction. Callers must
/// `cx.notify()` to re-render.
pub(crate) fn set_gesture_selected_dir(&mut self, dir: Option<GestureDirection>) {
Expand Down Expand Up @@ -232,14 +283,34 @@ fn set_control_hovered(
});
}

impl Render for MouseModelView {
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
impl MouseModelView {
/// Re-stamp every action-picker input's placeholder after a language
/// switch, split out of `render` to keep it under clippy's line budget.
fn localize_action_picker_inputs(&self, window: &mut Window, cx: &mut Context<Self>) {
crate::ui::components::localize_placeholder(
&self.action_search,
tr!("actions.search_actions"),
window,
cx,
);
crate::ui::components::localize_placeholder(
&self.custom_shortcut_input,
tr!("action_ring.shortcut_e_g_cmd_plus_shift_plus_p"),
window,
cx,
);
crate::ui::components::localize_placeholder(
&self.custom_application_input,
tr!("action_ring.application_folder_path_or_url"),
window,
cx,
);
}
}

impl Render for MouseModelView {
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
self.localize_action_picker_inputs(window, cx);
let (empty_bindings, empty_gesture_maps) = (BTreeMap::new(), BTreeMap::new());
let MouseWorkspaceData {
device_key,
Expand Down Expand Up @@ -1070,6 +1141,37 @@ mod tests {
cx.run_until_parked();
}

#[gpui::test]
fn clearing_custom_action_drafts_resets_text_and_invalid_state(cx: &mut TestAppContext) {
cx.update(gpui_component::init);
install_app_state(cx);
let (view, cx) = cx.add_window_view(MouseModelView::new);
cx.run_until_parked();

cx.update(|window, cx| {
view.update(cx, |view, cx| {
view.custom_shortcut_input
.update(cx, |input, cx| input.set_value("Cmd+K", window, cx));
view.custom_application_input
.update(cx, |input, cx| input.set_value("/bin/true", window, cx));
view.custom_shortcut_invalid = true;
view.custom_application_invalid = true;

view.clear_custom_action_drafts(window, cx);
});
});

view.update(cx, |view, cx| {
assert_eq!(view.custom_shortcut_input.read(cx).value(), "");
assert_eq!(view.custom_application_input.read(cx).value(), "");
assert!(!view.custom_shortcut_invalid);
assert!(!view.custom_application_invalid);
});
drop(view);
cx.update(|window, _| window.remove_window());
cx.run_until_parked();
}

#[test]
fn active_thumbwheel_directions_highlight_the_paired_control() {
assert_eq!(
Expand Down
1 change: 1 addition & 0 deletions crates/openlogi-ui/locales/be.toml
Original file line number Diff line number Diff line change
Expand Up @@ -538,6 +538,7 @@ custom_shortcut = "Уласнае спалучэнне клавіш"
shortcut_e_g_cmd_plus_shift_plus_p = "Спалучэнне, напр. Cmd+Shift+P"
application_folder_path_or_url = "Праграма, шлях да папкі або URL"
open_application_or_folder = "Адкрыць праграму або папку"
custom_action_invalid_input = "Couldn't recognize that input."

[keyboard]
editor_unavailable = "Рэдактар недаступны"
Expand Down
1 change: 1 addition & 0 deletions crates/openlogi-ui/locales/da.toml
Original file line number Diff line number Diff line change
Expand Up @@ -538,6 +538,7 @@ custom_shortcut = "Brugerdefineret genvej"
shortcut_e_g_cmd_plus_shift_plus_p = "Genvej, f.eks. Cmd+Shift+P"
application_folder_path_or_url = "Program, mappesti eller URL"
open_application_or_folder = "Åbn program eller mappe"
custom_action_invalid_input = "Couldn't recognize that input."

[keyboard]
editor_unavailable = "Editor ikke tilgængelig"
Expand Down
1 change: 1 addition & 0 deletions crates/openlogi-ui/locales/de.toml
Original file line number Diff line number Diff line change
Expand Up @@ -538,6 +538,7 @@ custom_shortcut = "Benutzerdefinierter Kurzbefehl"
shortcut_e_g_cmd_plus_shift_plus_p = "Tastenkürzel, z. B. Cmd+Shift+P"
application_folder_path_or_url = "Anwendung, Ordnerpfad oder URL"
open_application_or_folder = "Anwendung oder Ordner öffnen"
custom_action_invalid_input = "Couldn't recognize that input."

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Validation message remains English

The new validation message is copied verbatim from English into every non-English catalog. When localized users submit an invalid shortcut or application target, the feedback appears in English. This conflicts with the repository's documented localization convention that new catalog entries must never use English fill-in and makes the new error less useful to those users.

Fix in Codex Fix in Claude Code


[keyboard]
editor_unavailable = "Editor nicht verfügbar"
Expand Down
1 change: 1 addition & 0 deletions crates/openlogi-ui/locales/el.toml
Original file line number Diff line number Diff line change
Expand Up @@ -538,6 +538,7 @@ custom_shortcut = "Προσαρμοσμένη συντόμευση"
shortcut_e_g_cmd_plus_shift_plus_p = "Συντόμευση, π.χ. Cmd+Shift+P"
application_folder_path_or_url = "Εφαρμογή, διαδρομή φακέλου ή URL"
open_application_or_folder = "Άνοιγμα εφαρμογής ή φακέλου"
custom_action_invalid_input = "Couldn't recognize that input."

[keyboard]
editor_unavailable = "Ο επεξεργαστής δεν είναι διαθέσιμος"
Expand Down
1 change: 1 addition & 0 deletions crates/openlogi-ui/locales/en.toml
Original file line number Diff line number Diff line change
Expand Up @@ -538,6 +538,7 @@ custom_shortcut = "Custom shortcut"
shortcut_e_g_cmd_plus_shift_plus_p = "Shortcut, e.g., Cmd+Shift+P"
application_folder_path_or_url = "Application or folder path, or URL"
open_application_or_folder = "Open application or folder"
custom_action_invalid_input = "Couldn't recognize that input."

[keyboard]
editor_unavailable = "Editor unavailable"
Expand Down
1 change: 1 addition & 0 deletions crates/openlogi-ui/locales/es.toml
Original file line number Diff line number Diff line change
Expand Up @@ -538,6 +538,7 @@ custom_shortcut = "Atajo personalizado"
shortcut_e_g_cmd_plus_shift_plus_p = "Atajo, p. ej. Cmd+Shift+P"
application_folder_path_or_url = "Ruta de aplicación o carpeta, o URL"
open_application_or_folder = "Abrir aplicación o carpeta"
custom_action_invalid_input = "Couldn't recognize that input."

[keyboard]
editor_unavailable = "Editor no disponible"
Expand Down
1 change: 1 addition & 0 deletions crates/openlogi-ui/locales/fi.toml
Original file line number Diff line number Diff line change
Expand Up @@ -538,6 +538,7 @@ custom_shortcut = "Mukautettu pikanäppäin"
shortcut_e_g_cmd_plus_shift_plus_p = "Pikanäppäin, esim. Cmd+Shift+P"
application_folder_path_or_url = "Sovellus, kansiopolku tai URL"
open_application_or_folder = "Avaa sovellus tai kansio"
custom_action_invalid_input = "Couldn't recognize that input."

[keyboard]
editor_unavailable = "Muokkain ei ole käytettävissä"
Expand Down
Loading
Loading