feat: add Wayland popup context menu to TextInput
This commit is contained in:
parent
c6cf3db29d
commit
d751855c74
3 changed files with 301 additions and 91 deletions
|
|
@ -109,16 +109,6 @@ fn send_popup_direct(settings: iced_runtime::platform_specific::wayland::popup::
|
|||
);
|
||||
}
|
||||
|
||||
/// Sends a destroy popup action directly to the SCTK event loop.
|
||||
#[cfg(feature = "wayland")]
|
||||
fn send_destroy_popup_direct(id: iced_core::window::Id) {
|
||||
iced_winit::send_wayland_action_direct(
|
||||
iced_runtime::platform_specific::wayland::Action::Popup(
|
||||
iced_runtime::platform_specific::wayland::popup::Action::Destroy { id },
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Creates a context menu overlay for any widget implementing
|
||||
/// [`HasSelectableText`].
|
||||
///
|
||||
|
|
|
|||
|
|
@ -43,6 +43,12 @@ thread_local! {
|
|||
static LAST_FOCUS_UPDATE: LazyCell<Cell<Instant>> = LazyCell::new(|| Cell::new(Instant::now()));
|
||||
}
|
||||
|
||||
/// Notify all text inputs that a different widget has taken focus.
|
||||
/// This causes any focused text input to unfocus on its next layout.
|
||||
pub fn notify_focus_change() {
|
||||
LAST_FOCUS_UPDATE.with(|x| x.set(Instant::now()));
|
||||
}
|
||||
|
||||
/// Creates a new [`TextInput`].
|
||||
///
|
||||
/// [`TextInput`]: widget::TextInput
|
||||
|
|
@ -577,6 +583,17 @@ where
|
|||
self.drag_threshold = drag_threshold;
|
||||
self
|
||||
}
|
||||
|
||||
fn uses_popup_context_menu(&self) -> bool {
|
||||
#[cfg(feature = "wayland")]
|
||||
if matches!(
|
||||
crate::app::cosmic::WINDOWING_SYSTEM.get(),
|
||||
Some(crate::app::cosmic::WindowingSystem::Wayland)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
impl<Message> Widget<Message, crate::Theme, crate::Renderer> for TextInput<'_, Message>
|
||||
|
|
@ -843,6 +860,24 @@ where
|
|||
viewport: &Rectangle,
|
||||
translation: Vector,
|
||||
) -> Option<overlay::Element<'b, Message, crate::Theme, crate::Renderer>> {
|
||||
if !self.uses_popup_context_menu() {
|
||||
let has_context_menu = tree
|
||||
.state
|
||||
.downcast_ref::<State>()
|
||||
.context_menu_position
|
||||
.is_some();
|
||||
if has_context_menu {
|
||||
let menu_bar_state = tree.state.downcast_ref::<State>().menu_bar_state.clone();
|
||||
return crate::widget::text_context_menu::context_menu_overlay(
|
||||
self,
|
||||
tree,
|
||||
self.on_input.as_deref(),
|
||||
translation,
|
||||
menu_bar_state,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let mut layout_ = Vec::with_capacity(2);
|
||||
if self.leading_icon.is_some() {
|
||||
let mut children = self.text_layout(layout).children();
|
||||
|
|
@ -857,7 +892,7 @@ where
|
|||
}
|
||||
layout_.push(children.next().unwrap());
|
||||
};
|
||||
let children = self
|
||||
let children: Vec<overlay::Element<'_, Message, crate::Theme, crate::Renderer>> = self
|
||||
.leading_icon
|
||||
.iter_mut()
|
||||
.chain(self.trailing_icon.iter_mut())
|
||||
|
|
@ -868,7 +903,7 @@ where
|
|||
.as_widget_mut()
|
||||
.overlay(state, layout, renderer, viewport, translation)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
.collect();
|
||||
|
||||
(!children.is_empty()).then(|| Group::with_children(children).overlay())
|
||||
}
|
||||
|
|
@ -936,6 +971,23 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
// Unfocus on any click outside widget bounds.
|
||||
if matches!(
|
||||
event,
|
||||
Event::Mouse(mouse::Event::ButtonPressed(_))
|
||||
| Event::Touch(touch::Event::FingerPressed { .. })
|
||||
) && cursor_position.position_over(layout.bounds()).is_none()
|
||||
{
|
||||
let state = tree.state.downcast_mut::<State>();
|
||||
state.is_focused = None;
|
||||
state.context_menu_position = None;
|
||||
state.dragging_state = None;
|
||||
if let Some(on_unfocus) = self.on_unfocus.as_ref() {
|
||||
shell.publish(on_unfocus.clone());
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
let state = tree.state.downcast_mut::<State>();
|
||||
|
||||
if let Some(on_unfocus) = self.on_unfocus.as_ref() {
|
||||
|
|
@ -977,6 +1029,77 @@ where
|
|||
self.always_active,
|
||||
);
|
||||
|
||||
// On Wayland: if right-click just set context_menu_position, create a popup instead.
|
||||
#[cfg(feature = "wayland")]
|
||||
if matches!(
|
||||
crate::app::cosmic::WINDOWING_SYSTEM.get(),
|
||||
Some(crate::app::cosmic::WindowingSystem::Wayland)
|
||||
) {
|
||||
let state = tree.state.downcast_ref::<State>();
|
||||
if state.context_menu_position.is_some() {
|
||||
let selected_text = state
|
||||
.cursor()
|
||||
.selection(&state.tracked_value)
|
||||
.map(|(start, end)| state.tracked_value.select(start, end).to_string());
|
||||
let has_selection = selected_text.is_some();
|
||||
let click_position = state.context_menu_position.unwrap();
|
||||
let menu_bar_state = state.menu_bar_state.clone();
|
||||
let pending_action = state.pending_action.clone();
|
||||
|
||||
crate::widget::text_context_menu::create_text_context_popup(
|
||||
click_position,
|
||||
selected_text,
|
||||
true,
|
||||
has_selection,
|
||||
&menu_bar_state,
|
||||
&pending_action,
|
||||
renderer,
|
||||
viewport,
|
||||
cursor_position,
|
||||
);
|
||||
|
||||
let state = tree.state.downcast_mut::<State>();
|
||||
state.context_menu_position = None;
|
||||
}
|
||||
|
||||
// Process deferred actions from the popup.
|
||||
let state = tree.state.downcast_ref::<State>();
|
||||
let pending_action = state.pending_action.clone();
|
||||
if let Some(action) =
|
||||
crate::widget::text_context_menu::take_pending_action(&pending_action)
|
||||
{
|
||||
let state = tree.state.downcast_mut::<State>();
|
||||
match action {
|
||||
crate::widget::text_context_menu::TextCtxAction::Copy => {}
|
||||
crate::widget::text_context_menu::TextCtxAction::Cut => {
|
||||
let contents = state.delete_selection();
|
||||
if let Some(on_input) = self.on_input.as_deref() {
|
||||
shell.publish((on_input)(contents));
|
||||
}
|
||||
}
|
||||
crate::widget::text_context_menu::TextCtxAction::Paste => {
|
||||
let content: String = clipboard
|
||||
.read(iced_core::clipboard::Kind::Standard)
|
||||
.unwrap_or_default();
|
||||
let filtered: String =
|
||||
content.chars().filter(|c| !c.is_control()).collect();
|
||||
let contents = state.paste_text(&filtered);
|
||||
if let Some(on_input) = self.on_input.as_deref() {
|
||||
shell.publish((on_input)(contents));
|
||||
}
|
||||
}
|
||||
crate::widget::text_context_menu::TextCtxAction::SelectAll => {
|
||||
state.select_all();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Cleanup popup if menu closed.
|
||||
let state = tree.state.downcast_ref::<State>();
|
||||
let menu_bar_state = state.menu_bar_state.clone();
|
||||
crate::widget::text_context_menu::cleanup_text_popup(&menu_bar_state);
|
||||
}
|
||||
|
||||
let state = tree.state.downcast_mut::<State>();
|
||||
let value = if self.is_secure {
|
||||
self.value.secure()
|
||||
|
|
@ -1461,11 +1584,44 @@ pub fn update<'a, Message: Clone + 'static>(
|
|||
#[cold]
|
||||
fn cold() {}
|
||||
|
||||
let state = state();
|
||||
|
||||
// Any click outside clears focus and selection.
|
||||
if matches!(
|
||||
event,
|
||||
Event::Mouse(mouse::Event::ButtonPressed(_))
|
||||
| Event::Touch(touch::Event::FingerPressed { .. })
|
||||
) && cursor.position_over(layout.bounds()).is_none()
|
||||
{
|
||||
state.is_focused = None;
|
||||
state.context_menu_position = None;
|
||||
state.dragging_state = None;
|
||||
if let Some(on_unfocus) = on_unfocus {
|
||||
shell.publish(on_unfocus.clone());
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
match event {
|
||||
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Right)) => {
|
||||
if let Some(pos) = cursor.position_over(layout.bounds()) {
|
||||
if !state.is_focused() {
|
||||
state.focus();
|
||||
}
|
||||
state.context_menu_position = Some(pos);
|
||||
shell.capture_event();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
|
||||
| Event::Touch(touch::Event::FingerPressed { .. }) => {
|
||||
cold();
|
||||
let state = state();
|
||||
|
||||
if state.context_menu_position.take().is_some() {
|
||||
shell.capture_event();
|
||||
return;
|
||||
}
|
||||
|
||||
let click_position = if on_input.is_some() || manage_value {
|
||||
cursor.position_over(layout.bounds())
|
||||
|
|
@ -1694,7 +1850,6 @@ pub fn update<'a, Message: Clone + 'static>(
|
|||
Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left))
|
||||
| Event::Touch(touch::Event::FingerLifted { .. } | touch::Event::FingerLost { .. }) => {
|
||||
cold();
|
||||
let state = state();
|
||||
#[cfg(wayland_platform)]
|
||||
if matches!(state.dragging_state, Some(DraggingState::PrepareDnd(_))) {
|
||||
// clear selection and place cursor at click position
|
||||
|
|
@ -1722,8 +1877,6 @@ pub fn update<'a, Message: Clone + 'static>(
|
|||
}
|
||||
Event::Mouse(mouse::Event::CursorMoved { position })
|
||||
| Event::Touch(touch::Event::FingerMoved { position, .. }) => {
|
||||
let state = state();
|
||||
|
||||
if matches!(state.dragging_state, Some(DraggingState::Selection)) {
|
||||
let target = {
|
||||
let text_bounds = text_layout.bounds();
|
||||
|
|
@ -1812,7 +1965,6 @@ pub fn update<'a, Message: Clone + 'static>(
|
|||
modifiers,
|
||||
..
|
||||
}) => {
|
||||
let state = state();
|
||||
state.keyboard_modifiers = *modifiers;
|
||||
|
||||
if let Some(focus) = state.is_focused.as_mut().filter(|f| f.focused) {
|
||||
|
|
@ -2107,8 +2259,6 @@ pub fn update<'a, Message: Clone + 'static>(
|
|||
}
|
||||
}
|
||||
Event::Keyboard(keyboard::Event::KeyReleased { key, .. }) => {
|
||||
let state = state();
|
||||
|
||||
if state.is_focused() {
|
||||
match key {
|
||||
keyboard::Key::Character(c) if "v" == c => {
|
||||
|
|
@ -2130,73 +2280,65 @@ pub fn update<'a, Message: Clone + 'static>(
|
|||
}
|
||||
}
|
||||
Event::Keyboard(keyboard::Event::ModifiersChanged(modifiers)) => {
|
||||
let state = state();
|
||||
|
||||
state.keyboard_modifiers = *modifiers;
|
||||
}
|
||||
Event::InputMethod(event) => {
|
||||
let state = state();
|
||||
|
||||
match event {
|
||||
input_method::Event::Opened | input_method::Event::Closed => {
|
||||
state.preedit = matches!(event, input_method::Event::Opened)
|
||||
.then(input_method::Preedit::new);
|
||||
shell.capture_event();
|
||||
return;
|
||||
}
|
||||
input_method::Event::Preedit(content, selection) => {
|
||||
if state.is_focused() {
|
||||
state.preedit = Some(input_method::Preedit {
|
||||
content: content.to_owned(),
|
||||
selection: selection.clone(),
|
||||
text_size: Some(size.into()),
|
||||
});
|
||||
shell.capture_event();
|
||||
return;
|
||||
}
|
||||
}
|
||||
input_method::Event::Commit(text) => {
|
||||
let Some(focus) = state.is_focused.as_mut().filter(|f| f.focused) else {
|
||||
return;
|
||||
};
|
||||
let Some(on_input) = on_input else {
|
||||
return;
|
||||
};
|
||||
if state.is_read_only {
|
||||
return;
|
||||
}
|
||||
|
||||
focus.updated_at = Instant::now();
|
||||
LAST_FOCUS_UPDATE.with(|x| x.set(focus.updated_at));
|
||||
|
||||
let mut editor = Editor::new(unsecured_value, &mut state.cursor);
|
||||
editor.paste(Value::new(&text));
|
||||
|
||||
let contents = editor.contents();
|
||||
let unsecured_value = Value::new(&contents);
|
||||
let message = if let Some(paste) = &on_paste {
|
||||
(paste)(contents)
|
||||
} else {
|
||||
(on_input)(contents)
|
||||
};
|
||||
shell.publish(message);
|
||||
|
||||
state.is_pasting = None;
|
||||
let value = if is_secure {
|
||||
unsecured_value.secure()
|
||||
} else {
|
||||
unsecured_value
|
||||
};
|
||||
|
||||
update_cache(state, &value);
|
||||
Event::InputMethod(event) => match event {
|
||||
input_method::Event::Opened | input_method::Event::Closed => {
|
||||
state.preedit =
|
||||
matches!(event, input_method::Event::Opened).then(input_method::Preedit::new);
|
||||
shell.capture_event();
|
||||
return;
|
||||
}
|
||||
input_method::Event::Preedit(content, selection) => {
|
||||
if state.is_focused() {
|
||||
state.preedit = Some(input_method::Preedit {
|
||||
content: content.to_owned(),
|
||||
selection: selection.clone(),
|
||||
text_size: Some(size.into()),
|
||||
});
|
||||
shell.capture_event();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
Event::Window(window::Event::RedrawRequested(now)) => {
|
||||
let state = state();
|
||||
input_method::Event::Commit(text) => {
|
||||
let Some(focus) = state.is_focused.as_mut().filter(|f| f.focused) else {
|
||||
return;
|
||||
};
|
||||
let Some(on_input) = on_input else {
|
||||
return;
|
||||
};
|
||||
if state.is_read_only {
|
||||
return;
|
||||
}
|
||||
|
||||
focus.updated_at = Instant::now();
|
||||
LAST_FOCUS_UPDATE.with(|x| x.set(focus.updated_at));
|
||||
|
||||
let mut editor = Editor::new(unsecured_value, &mut state.cursor);
|
||||
editor.paste(Value::new(&text));
|
||||
|
||||
let contents = editor.contents();
|
||||
let unsecured_value = Value::new(&contents);
|
||||
let message = if let Some(paste) = &on_paste {
|
||||
(paste)(contents)
|
||||
} else {
|
||||
(on_input)(contents)
|
||||
};
|
||||
shell.publish(message);
|
||||
|
||||
state.is_pasting = None;
|
||||
let value = if is_secure {
|
||||
unsecured_value.secure()
|
||||
} else {
|
||||
unsecured_value
|
||||
};
|
||||
|
||||
update_cache(state, &value);
|
||||
shell.capture_event();
|
||||
return;
|
||||
}
|
||||
},
|
||||
Event::Window(window::Event::RedrawRequested(now)) => {
|
||||
if let Some(focus) = state.is_focused.as_mut().filter(|f| f.focused) {
|
||||
focus.now = *now;
|
||||
|
||||
|
|
@ -2215,7 +2357,6 @@ pub fn update<'a, Message: Clone + 'static>(
|
|||
#[cfg(wayland_platform)]
|
||||
Event::Dnd(DndEvent::Source(SourceEvent::Finished | SourceEvent::Cancelled)) => {
|
||||
cold();
|
||||
let state = state();
|
||||
if matches!(state.dragging_state, Some(DraggingState::Dnd(..))) {
|
||||
// TODO: restore value in text input
|
||||
state.dragging_state = None;
|
||||
|
|
@ -2234,7 +2375,6 @@ pub fn update<'a, Message: Clone + 'static>(
|
|||
},
|
||||
)) if *rectangle == Some(dnd_id) => {
|
||||
cold();
|
||||
let state = state();
|
||||
let is_clicked = text_layout.bounds().contains(Point {
|
||||
x: *x as f32,
|
||||
y: *y as f32,
|
||||
|
|
@ -2277,8 +2417,6 @@ pub fn update<'a, Message: Clone + 'static>(
|
|||
Event::Dnd(DndEvent::Offer(rectangle, OfferEvent::Motion { x, y }))
|
||||
if *rectangle == Some(dnd_id) =>
|
||||
{
|
||||
let state = state();
|
||||
|
||||
let target = {
|
||||
let text_bounds = text_layout.bounds();
|
||||
|
||||
|
|
@ -2304,7 +2442,6 @@ pub fn update<'a, Message: Clone + 'static>(
|
|||
#[cfg(wayland_platform)]
|
||||
Event::Dnd(DndEvent::Offer(rectangle, OfferEvent::Drop)) if *rectangle == Some(dnd_id) => {
|
||||
cold();
|
||||
let state = state();
|
||||
if let DndOfferState::HandlingOffer(mime_types, _action) = state.dnd_offer.clone() {
|
||||
let Some(mime_type) = SUPPORTED_TEXT_MIME_TYPES
|
||||
.iter()
|
||||
|
|
@ -2327,7 +2464,6 @@ pub fn update<'a, Message: Clone + 'static>(
|
|||
OfferEvent::Leave | OfferEvent::LeaveDestination,
|
||||
)) => {
|
||||
cold();
|
||||
let state = state();
|
||||
// ASHLEY TODO we should be able to reset but for now we don't if we are handling a
|
||||
// drop
|
||||
match state.dnd_offer {
|
||||
|
|
@ -2344,7 +2480,6 @@ pub fn update<'a, Message: Clone + 'static>(
|
|||
if *rectangle == Some(dnd_id) =>
|
||||
{
|
||||
cold();
|
||||
let state = state();
|
||||
if matches!(&state.dnd_offer, DndOfferState::Dropped) {
|
||||
state.dnd_offer = DndOfferState::None;
|
||||
if !SUPPORTED_TEXT_MIME_TYPES.contains(&mime_type.as_str()) || data.is_empty() {
|
||||
|
|
@ -2917,7 +3052,7 @@ pub(crate) enum DndOfferState {
|
|||
pub(crate) struct DndOfferState;
|
||||
|
||||
/// The state of a [`TextInput`].
|
||||
#[derive(Debug, Default, Clone)]
|
||||
#[derive(Default, Clone)]
|
||||
#[must_use]
|
||||
pub struct State {
|
||||
pub tracked_value: Value,
|
||||
|
|
@ -2940,6 +3075,19 @@ pub struct State {
|
|||
preedit: Option<Preedit>,
|
||||
keyboard_modifiers: keyboard::Modifiers,
|
||||
scroll_offset: f32,
|
||||
context_menu_position: Option<iced_core::Point>,
|
||||
pub(crate) menu_bar_state: crate::widget::menu::MenuBarState,
|
||||
pub(crate) pending_action: crate::widget::text_context_menu::PendingAction,
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for State {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("State")
|
||||
.field("is_secure", &self.is_secure)
|
||||
.field("is_read_only", &self.is_read_only)
|
||||
.field("dirty", &self.dirty)
|
||||
.finish_non_exhaustive()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
|
|
@ -2984,7 +3132,7 @@ impl State {
|
|||
cursor::State::Selection { start, end } => {
|
||||
let left = start.min(end);
|
||||
let right = end.max(start);
|
||||
Some(text[left..right].to_string())
|
||||
Some(value.select(left, right).to_string())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -3022,6 +3170,9 @@ impl State {
|
|||
keyboard_modifiers: keyboard::Modifiers::default(),
|
||||
scroll_offset: 0.0,
|
||||
dirty: false,
|
||||
context_menu_position: None,
|
||||
menu_bar_state: crate::widget::menu::MenuBarState::default(),
|
||||
pending_action: crate::widget::text_context_menu::pending_action(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -3066,7 +3217,7 @@ impl State {
|
|||
/// Unfocuses the [`TextInput`].
|
||||
#[cold]
|
||||
pub(super) fn unfocus(&mut self) {
|
||||
self.move_cursor_to_front();
|
||||
self.cursor.clear_selection();
|
||||
self.last_click = None;
|
||||
self.is_focused = self.is_focused.map(|mut f| {
|
||||
f.focused = false;
|
||||
|
|
@ -3108,6 +3259,33 @@ impl State {
|
|||
self.cursor.select_range(start, end);
|
||||
}
|
||||
|
||||
/// Returns the context menu position, if a context menu should be shown.
|
||||
pub fn context_menu_position(&self) -> Option<iced_core::Point> {
|
||||
self.context_menu_position
|
||||
}
|
||||
|
||||
/// Sets or clears the context menu position.
|
||||
pub fn set_context_menu_position(&mut self, pos: Option<iced_core::Point>) {
|
||||
self.context_menu_position = pos;
|
||||
}
|
||||
|
||||
/// Deletes the current selection and returns the new text content.
|
||||
pub fn delete_selection(&mut self) -> String {
|
||||
let mut editor = super::editor::Editor::new(&mut self.tracked_value, &mut self.cursor);
|
||||
editor.delete();
|
||||
editor.contents()
|
||||
}
|
||||
|
||||
/// Pastes text at the current cursor position and returns the new text content.
|
||||
pub fn paste_text(&mut self, text: &str) -> String {
|
||||
let paste_value = super::value::Value::new(text);
|
||||
let mut editor = super::editor::Editor::new(&mut self.tracked_value, &mut self.cursor);
|
||||
editor.paste(paste_value);
|
||||
let contents = editor.contents();
|
||||
self.tracked_value = super::value::Value::new(&contents);
|
||||
contents
|
||||
}
|
||||
|
||||
pub(super) fn setting_selection(&mut self, value: &Value, bounds: Rectangle<f32>, target: f32) {
|
||||
let (position, affinity) = find_cursor_position(bounds, value, self, target)
|
||||
.unwrap_or((0, text::Affinity::Before));
|
||||
|
|
@ -3335,3 +3513,47 @@ fn effective_alignment(paragraph: &impl text::Paragraph) -> alignment::Horizonta
|
|||
alignment::Horizontal::Left
|
||||
}
|
||||
}
|
||||
|
||||
use iced_core::widget::tree::Tree as WidgetTree;
|
||||
|
||||
impl<Message: Clone + 'static> iced_core::widget::text::HasSelectableText
|
||||
for TextInput<'_, Message>
|
||||
{
|
||||
fn selected_text(&self, tree: &WidgetTree) -> Option<String> {
|
||||
let state = tree.state.downcast_ref::<State>();
|
||||
let (start, end) = state.cursor().selection(&state.tracked_value)?;
|
||||
Some(state.tracked_value.select(start, end).to_string())
|
||||
}
|
||||
|
||||
fn select_all(&self, tree: &mut WidgetTree) {
|
||||
let state = tree.state.downcast_mut::<State>();
|
||||
state.select_all();
|
||||
}
|
||||
|
||||
fn is_editable(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn is_focused(&self, tree: &WidgetTree) -> bool {
|
||||
tree.state.downcast_ref::<State>().is_focused()
|
||||
}
|
||||
|
||||
fn context_menu_position(&self, tree: &WidgetTree) -> Option<iced_core::Point> {
|
||||
tree.state.downcast_ref::<State>().context_menu_position
|
||||
}
|
||||
|
||||
fn set_context_menu_position(&self, tree: &mut WidgetTree, pos: Option<iced_core::Point>) {
|
||||
tree.state.downcast_mut::<State>().context_menu_position = pos;
|
||||
}
|
||||
|
||||
fn delete_selection(&self, tree: &mut WidgetTree) -> Option<String> {
|
||||
let state = tree.state.downcast_mut::<State>();
|
||||
Some(state.delete_selection())
|
||||
}
|
||||
|
||||
fn paste_text(&self, tree: &mut WidgetTree, text: &str) -> Option<String> {
|
||||
let filtered: String = text.chars().filter(|c| !c.is_control()).collect();
|
||||
let state = tree.state.downcast_mut::<State>();
|
||||
Some(state.paste_text(&filtered))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -301,8 +301,6 @@ impl<'a, Message: 'static + Clone, TopLevelMessage: 'static + Clone>
|
|||
}
|
||||
let content_layout = layout.children().next().unwrap();
|
||||
|
||||
let state = tree.state.downcast_ref::<State>();
|
||||
|
||||
let styling = theme.style(&self.style);
|
||||
|
||||
let icon_color = styling.icon_color.unwrap_or(renderer_style.icon_color);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue