A9: right-click context menu (Cut/Copy/Paste/Select All) for all text fields
iced 0.14 ships no native right-click menu on text_input. Add a custom ContextInput widget (src/widget/context_input.rs) that wraps text_input, intercepts right-click to read the inner text_input::State selection, and renders a themed 4-action overlay menu operating on that selection. - Pure, grapheme-indexed edit seam (copy/cut/paste/select_all over iced text_input::Value), unit-tested for ASCII and multi-byte/emoji. - iced::advanced Widget + overlay::Overlay; clipboard via &mut dyn Clipboard, edits published through the existing on_input/on_paste. - Cut/Copy disabled on empty selection (and on secure fields), Select All disabled on empty field, Paste always enabled; dismiss on click-out / Esc / item-click. - Route all 10 text_input call sites in app/mod.rs through context_input. - Cargo.toml: enable iced "advanced" feature (same crate, no new dep). 459 lib tests (+5), clippy --all-targets clean, release green. Implemented by Codex (gpt-5.5), senior-audited against the 5-point brief and re-verified (tests/clippy/release) here. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Co-Authored-By: Codex (gpt-5.5) <noreply@openai.com>
This commit is contained in:
+13
-11
@@ -11,12 +11,14 @@ use crate::config::{AppConfig, NetworkMode, RecordingMode, RoomLayout};
|
||||
use crate::hotkeys::{format_binding, HotkeyAction, HotkeyContext, KeyBinding};
|
||||
use crate::presence::PresenceMode;
|
||||
use crate::theme::{AppTheme, Palette};
|
||||
use crate::widget::context_input::context_input;
|
||||
|
||||
use iced::widget::{
|
||||
container, column, row, text, button, text_input, scrollable, slider, checkbox, pick_list,
|
||||
container, column, row, text, button, scrollable, slider, checkbox, pick_list,
|
||||
radio, tooltip, progress_bar, canvas, Canvas, Column, stack, mouse_area,
|
||||
rich_text, span, responsive,
|
||||
};
|
||||
use iced::widget::text_input;
|
||||
use iced::widget::canvas::{Action, Frame, Geometry, Path, Program};
|
||||
use iced::{
|
||||
Color, Background, Border, Element, Subscription, Task, Theme, Event, keyboard, mouse,
|
||||
@@ -2624,7 +2626,7 @@ fn connect_card(state: &AppState) -> Element<'_, AppMessage> {
|
||||
let nickname_input = column![
|
||||
text("Nickname").size(14).color(color_subtext),
|
||||
vertical_space(4.0),
|
||||
text_input("Enter nickname...", &state.name)
|
||||
context_input("Enter nickname...", &state.name)
|
||||
.on_input(AppMessage::NicknameChanged)
|
||||
.style(t_style)
|
||||
.padding(10)
|
||||
@@ -2633,7 +2635,7 @@ fn connect_card(state: &AppState) -> Element<'_, AppMessage> {
|
||||
// Optional cosmetic room label (W7) above the Create button: it rides in the
|
||||
// minted ticket so everyone who joins inherits "in <name>". Enter also creates.
|
||||
let create_group = column![
|
||||
text_input("Room name (optional)", &state.room_name_input)
|
||||
context_input("Room name (optional)", &state.room_name_input)
|
||||
.on_input(AppMessage::RoomNameChanged)
|
||||
.on_submit(AppMessage::CreatePressed)
|
||||
.style(t_style)
|
||||
@@ -2649,7 +2651,7 @@ fn connect_card(state: &AppState) -> Element<'_, AppMessage> {
|
||||
let join_group = column![
|
||||
text("Join Existing Room").size(14).color(color_subtext),
|
||||
vertical_space(4.0),
|
||||
text_input("Paste room ticket here...", &state.ticket_input)
|
||||
context_input("Paste room ticket here...", &state.ticket_input)
|
||||
.on_input(AppMessage::TicketInputChanged)
|
||||
.style(t_style)
|
||||
.padding(10),
|
||||
@@ -2875,7 +2877,7 @@ fn friends_panel(state: &AppState) -> Element<'_, AppMessage> {
|
||||
};
|
||||
friend_rows = friend_rows.push(
|
||||
row![
|
||||
text_input("name", &f.name)
|
||||
context_input("name", &f.name)
|
||||
.on_input(move |v| AppMessage::RenameFriend(fid, v))
|
||||
.style(t_style)
|
||||
.padding(6)
|
||||
@@ -2909,13 +2911,13 @@ fn friends_panel(state: &AppState) -> Element<'_, AppMessage> {
|
||||
};
|
||||
|
||||
let add_form = column![
|
||||
text_input("Friend's node ID", &state.friend_add_id)
|
||||
context_input("Friend's node ID", &state.friend_add_id)
|
||||
.on_input(AppMessage::FriendAddIdChanged)
|
||||
.style(t_style)
|
||||
.padding(6),
|
||||
vertical_space(6.0),
|
||||
row![
|
||||
text_input("Name (optional)", &state.friend_add_name)
|
||||
context_input("Name (optional)", &state.friend_add_name)
|
||||
.on_input(AppMessage::FriendAddNameChanged)
|
||||
.style(t_style)
|
||||
.padding(6)
|
||||
@@ -3164,7 +3166,7 @@ fn view(state: &AppState) -> Element<'_, AppMessage> {
|
||||
horizontal_space(),
|
||||
validation_widget,
|
||||
].spacing(6).align_y(iced::alignment::Vertical::Center),
|
||||
text_input("Default (embedded)...", path)
|
||||
context_input("Default (embedded)...", path)
|
||||
.on_input(move |val| AppMessage::CustomSoundPathChanged(sound, val))
|
||||
.style(t_style)
|
||||
.padding(8)
|
||||
@@ -3834,10 +3836,10 @@ fn view(state: &AppState) -> Element<'_, AppMessage> {
|
||||
text("Steam games are detected automatically. For other launchers, map an executable name to a display name.")
|
||||
.size(11).color(color_subtext),
|
||||
row![
|
||||
text_input("executable (e.g. hl2_linux)", &state.game_map_exe_input)
|
||||
context_input("executable (e.g. hl2_linux)", &state.game_map_exe_input)
|
||||
.on_input(AppMessage::GameMapExeChanged)
|
||||
.width(iced::Length::Fill),
|
||||
text_input("shown name (e.g. Half-Life 2)", &state.game_map_name_input)
|
||||
context_input("shown name (e.g. Half-Life 2)", &state.game_map_name_input)
|
||||
.on_input(AppMessage::GameMapNameChanged)
|
||||
.width(iced::Length::Fill),
|
||||
button(text("Add").size(13)).on_press(AppMessage::AddGameMapping),
|
||||
@@ -4836,7 +4838,7 @@ fn view(state: &AppState) -> Element<'_, AppMessage> {
|
||||
.on_press(AppMessage::PickAttachmentFile)
|
||||
.style(b_style(color_surface, color_overlay, color_text, 6.0))
|
||||
.padding(8),
|
||||
text_input("Message the room…", &state.chat_input)
|
||||
context_input("Message the room…", &state.chat_input)
|
||||
.on_input(AppMessage::ChatInputChanged)
|
||||
.on_submit(AppMessage::ChatSubmit)
|
||||
.style(t_style)
|
||||
|
||||
Reference in New Issue
Block a user