Draft Shell:request_redraw API
... and implement `TextInput` cursor blink 🎉
This commit is contained in:
parent
7ccd87c36b
commit
7354f68b3c
12 changed files with 267 additions and 111 deletions
|
|
@ -6,5 +6,5 @@ edition = "2021"
|
||||||
publish = false
|
publish = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
iced = { path = "../.." }
|
iced = { path = "../..", features = ["debug"] }
|
||||||
iced_native = { path = "../../native" }
|
iced_native = { path = "../../native" }
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,12 @@
|
||||||
use iced::alignment;
|
use iced::alignment;
|
||||||
use iced::executor;
|
use iced::executor;
|
||||||
use iced::widget::{button, checkbox, container, text, Column};
|
use iced::widget::{button, checkbox, container, text, Column};
|
||||||
|
use iced::window;
|
||||||
use iced::{
|
use iced::{
|
||||||
Alignment, Application, Command, Element, Length, Settings, Subscription,
|
Alignment, Application, Command, Element, Length, Settings, Subscription,
|
||||||
Theme,
|
Theme,
|
||||||
};
|
};
|
||||||
use iced_native::{window, Event};
|
use iced_native::Event;
|
||||||
|
|
||||||
pub fn main() -> iced::Result {
|
pub fn main() -> iced::Result {
|
||||||
Events::run(Settings {
|
Events::run(Settings {
|
||||||
|
|
@ -18,7 +19,6 @@ pub fn main() -> iced::Result {
|
||||||
struct Events {
|
struct Events {
|
||||||
last: Vec<iced_native::Event>,
|
last: Vec<iced_native::Event>,
|
||||||
enabled: bool,
|
enabled: bool,
|
||||||
should_exit: bool,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
|
|
@ -50,31 +50,29 @@ impl Application for Events {
|
||||||
if self.last.len() > 5 {
|
if self.last.len() > 5 {
|
||||||
let _ = self.last.remove(0);
|
let _ = self.last.remove(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Command::none()
|
||||||
}
|
}
|
||||||
Message::EventOccurred(event) => {
|
Message::EventOccurred(event) => {
|
||||||
if let Event::Window(window::Event::CloseRequested) = event {
|
if let Event::Window(window::Event::CloseRequested) = event {
|
||||||
self.should_exit = true;
|
window::close()
|
||||||
|
} else {
|
||||||
|
Command::none()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Message::Toggled(enabled) => {
|
Message::Toggled(enabled) => {
|
||||||
self.enabled = enabled;
|
self.enabled = enabled;
|
||||||
}
|
|
||||||
Message::Exit => {
|
|
||||||
self.should_exit = true;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Command::none()
|
Command::none()
|
||||||
|
}
|
||||||
|
Message::Exit => window::close(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn subscription(&self) -> Subscription<Message> {
|
fn subscription(&self) -> Subscription<Message> {
|
||||||
iced_native::subscription::events().map(Message::EventOccurred)
|
iced_native::subscription::events().map(Message::EventOccurred)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn should_exit(&self) -> bool {
|
|
||||||
self.should_exit
|
|
||||||
}
|
|
||||||
|
|
||||||
fn view(&self) -> Element<Message> {
|
fn view(&self) -> Element<Message> {
|
||||||
let events = Column::with_children(
|
let events = Column::with_children(
|
||||||
self.last
|
self.last
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
|
use iced::executor;
|
||||||
use iced::widget::{button, column, container};
|
use iced::widget::{button, column, container};
|
||||||
use iced::{Alignment, Element, Length, Sandbox, Settings};
|
use iced::window;
|
||||||
|
use iced::{Alignment, Application, Command, Element, Length, Settings, Theme};
|
||||||
|
|
||||||
pub fn main() -> iced::Result {
|
pub fn main() -> iced::Result {
|
||||||
Exit::run(Settings::default())
|
Exit::run(Settings::default())
|
||||||
|
|
@ -8,7 +10,6 @@ pub fn main() -> iced::Result {
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
struct Exit {
|
struct Exit {
|
||||||
show_confirm: bool,
|
show_confirm: bool,
|
||||||
exit: bool,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
|
@ -17,28 +18,27 @@ enum Message {
|
||||||
Exit,
|
Exit,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Sandbox for Exit {
|
impl Application for Exit {
|
||||||
|
type Executor = executor::Default;
|
||||||
type Message = Message;
|
type Message = Message;
|
||||||
|
type Theme = Theme;
|
||||||
|
type Flags = ();
|
||||||
|
|
||||||
fn new() -> Self {
|
fn new(_flags: ()) -> (Self, Command<Message>) {
|
||||||
Self::default()
|
(Self::default(), Command::none())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn title(&self) -> String {
|
fn title(&self) -> String {
|
||||||
String::from("Exit - Iced")
|
String::from("Exit - Iced")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn should_exit(&self) -> bool {
|
fn update(&mut self, message: Message) -> Command<Message> {
|
||||||
self.exit
|
|
||||||
}
|
|
||||||
|
|
||||||
fn update(&mut self, message: Message) {
|
|
||||||
match message {
|
match message {
|
||||||
Message::Confirm => {
|
Message::Confirm => window::close(),
|
||||||
self.exit = true;
|
|
||||||
}
|
|
||||||
Message::Exit => {
|
Message::Exit => {
|
||||||
self.show_confirm = true;
|
self.show_confirm = true;
|
||||||
|
|
||||||
|
Command::none()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -36,11 +36,11 @@ pub trait Renderer: Sized {
|
||||||
f: impl FnOnce(&mut Self),
|
f: impl FnOnce(&mut Self),
|
||||||
);
|
);
|
||||||
|
|
||||||
/// Clears all of the recorded primitives in the [`Renderer`].
|
|
||||||
fn clear(&mut self);
|
|
||||||
|
|
||||||
/// Fills a [`Quad`] with the provided [`Background`].
|
/// Fills a [`Quad`] with the provided [`Background`].
|
||||||
fn fill_quad(&mut self, quad: Quad, background: impl Into<Background>);
|
fn fill_quad(&mut self, quad: Quad, background: impl Into<Background>);
|
||||||
|
|
||||||
|
/// Clears all of the recorded primitives in the [`Renderer`].
|
||||||
|
fn clear(&mut self);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A polygon with four sides.
|
/// A polygon with four sides.
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
use std::time::Instant;
|
||||||
|
|
||||||
/// A connection to the state of a shell.
|
/// A connection to the state of a shell.
|
||||||
///
|
///
|
||||||
/// A [`Widget`] can leverage a [`Shell`] to trigger changes in an application,
|
/// A [`Widget`] can leverage a [`Shell`] to trigger changes in an application,
|
||||||
|
|
@ -7,6 +9,7 @@
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Shell<'a, Message> {
|
pub struct Shell<'a, Message> {
|
||||||
messages: &'a mut Vec<Message>,
|
messages: &'a mut Vec<Message>,
|
||||||
|
redraw_requested_at: Option<Instant>,
|
||||||
is_layout_invalid: bool,
|
is_layout_invalid: bool,
|
||||||
are_widgets_invalid: bool,
|
are_widgets_invalid: bool,
|
||||||
}
|
}
|
||||||
|
|
@ -16,11 +19,47 @@ impl<'a, Message> Shell<'a, Message> {
|
||||||
pub fn new(messages: &'a mut Vec<Message>) -> Self {
|
pub fn new(messages: &'a mut Vec<Message>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
messages,
|
messages,
|
||||||
|
redraw_requested_at: None,
|
||||||
is_layout_invalid: false,
|
is_layout_invalid: false,
|
||||||
are_widgets_invalid: false,
|
are_widgets_invalid: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Publish the given `Message` for an application to process it.
|
||||||
|
pub fn publish(&mut self, message: Message) {
|
||||||
|
self.messages.push(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Requests a new frame to be drawn at the given [`Instant`].
|
||||||
|
pub fn request_redraw(&mut self, at: Instant) {
|
||||||
|
match self.redraw_requested_at {
|
||||||
|
None => {
|
||||||
|
self.redraw_requested_at = Some(at);
|
||||||
|
}
|
||||||
|
Some(current) if at < current => {
|
||||||
|
self.redraw_requested_at = Some(at);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the requested [`Instant`] a redraw should happen, if any.
|
||||||
|
pub fn redraw_requested_at(&self) -> Option<Instant> {
|
||||||
|
self.redraw_requested_at
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns whether the current layout is invalid or not.
|
||||||
|
pub fn is_layout_invalid(&self) -> bool {
|
||||||
|
self.is_layout_invalid
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Invalidates the current application layout.
|
||||||
|
///
|
||||||
|
/// The shell will relayout the application widgets.
|
||||||
|
pub fn invalidate_layout(&mut self) {
|
||||||
|
self.is_layout_invalid = true;
|
||||||
|
}
|
||||||
|
|
||||||
/// Triggers the given function if the layout is invalid, cleaning it in the
|
/// Triggers the given function if the layout is invalid, cleaning it in the
|
||||||
/// process.
|
/// process.
|
||||||
pub fn revalidate_layout(&mut self, f: impl FnOnce()) {
|
pub fn revalidate_layout(&mut self, f: impl FnOnce()) {
|
||||||
|
|
@ -31,21 +70,10 @@ impl<'a, Message> Shell<'a, Message> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns whether the current layout is invalid or not.
|
/// Returns whether the widgets of the current application have been
|
||||||
pub fn is_layout_invalid(&self) -> bool {
|
/// invalidated.
|
||||||
self.is_layout_invalid
|
pub fn are_widgets_invalid(&self) -> bool {
|
||||||
}
|
self.are_widgets_invalid
|
||||||
|
|
||||||
/// Publish the given `Message` for an application to process it.
|
|
||||||
pub fn publish(&mut self, message: Message) {
|
|
||||||
self.messages.push(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Invalidates the current application layout.
|
|
||||||
///
|
|
||||||
/// The shell will relayout the application widgets.
|
|
||||||
pub fn invalidate_layout(&mut self) {
|
|
||||||
self.is_layout_invalid = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Invalidates the current application widgets.
|
/// Invalidates the current application widgets.
|
||||||
|
|
@ -62,16 +90,14 @@ impl<'a, Message> Shell<'a, Message> {
|
||||||
pub fn merge<B>(&mut self, other: Shell<'_, B>, f: impl Fn(B) -> Message) {
|
pub fn merge<B>(&mut self, other: Shell<'_, B>, f: impl Fn(B) -> Message) {
|
||||||
self.messages.extend(other.messages.drain(..).map(f));
|
self.messages.extend(other.messages.drain(..).map(f));
|
||||||
|
|
||||||
|
if let Some(at) = other.redraw_requested_at {
|
||||||
|
self.request_redraw(at);
|
||||||
|
}
|
||||||
|
|
||||||
self.is_layout_invalid =
|
self.is_layout_invalid =
|
||||||
self.is_layout_invalid || other.is_layout_invalid;
|
self.is_layout_invalid || other.is_layout_invalid;
|
||||||
|
|
||||||
self.are_widgets_invalid =
|
self.are_widgets_invalid =
|
||||||
self.are_widgets_invalid || other.are_widgets_invalid;
|
self.are_widgets_invalid || other.are_widgets_invalid;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns whether the widgets of the current application have been
|
|
||||||
/// invalidated.
|
|
||||||
pub fn are_widgets_invalid(&self) -> bool {
|
|
||||||
self.are_widgets_invalid
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
//! Listen to external events in your application.
|
//! Listen to external events in your application.
|
||||||
use crate::event::{self, Event};
|
use crate::event::{self, Event};
|
||||||
|
use crate::window;
|
||||||
use crate::Hasher;
|
use crate::Hasher;
|
||||||
|
|
||||||
use iced_futures::futures::{self, Future, Stream};
|
use iced_futures::futures::{self, Future, Stream};
|
||||||
|
|
@ -33,7 +34,7 @@ pub type Tracker =
|
||||||
|
|
||||||
pub use iced_futures::subscription::Recipe;
|
pub use iced_futures::subscription::Recipe;
|
||||||
|
|
||||||
/// Returns a [`Subscription`] to all the runtime events.
|
/// Returns a [`Subscription`] to all the ignored runtime events.
|
||||||
///
|
///
|
||||||
/// This subscription will notify your application of any [`Event`] that was
|
/// This subscription will notify your application of any [`Event`] that was
|
||||||
/// not captured by any widget.
|
/// not captured by any widget.
|
||||||
|
|
@ -65,7 +66,10 @@ where
|
||||||
use futures::stream::StreamExt;
|
use futures::stream::StreamExt;
|
||||||
|
|
||||||
events.filter_map(move |(event, status)| {
|
events.filter_map(move |(event, status)| {
|
||||||
future::ready(f(event, status))
|
future::ready(match event {
|
||||||
|
Event::Window(window::Event::RedrawRequested(_)) => None,
|
||||||
|
_ => f(event, status),
|
||||||
|
})
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,8 @@ use crate::renderer;
|
||||||
use crate::widget;
|
use crate::widget;
|
||||||
use crate::{Clipboard, Element, Layout, Point, Rectangle, Shell, Size};
|
use crate::{Clipboard, Element, Layout, Point, Rectangle, Shell, Size};
|
||||||
|
|
||||||
|
use std::time::Instant;
|
||||||
|
|
||||||
/// A set of interactive graphical elements with a specific [`Layout`].
|
/// A set of interactive graphical elements with a specific [`Layout`].
|
||||||
///
|
///
|
||||||
/// It can be updated and drawn.
|
/// It can be updated and drawn.
|
||||||
|
|
@ -188,7 +190,9 @@ where
|
||||||
) -> (State, Vec<event::Status>) {
|
) -> (State, Vec<event::Status>) {
|
||||||
use std::mem::ManuallyDrop;
|
use std::mem::ManuallyDrop;
|
||||||
|
|
||||||
let mut state = State::Updated;
|
let mut outdated = false;
|
||||||
|
let mut redraw_requested_at = None;
|
||||||
|
|
||||||
let mut manual_overlay =
|
let mut manual_overlay =
|
||||||
ManuallyDrop::new(self.root.as_widget_mut().overlay(
|
ManuallyDrop::new(self.root.as_widget_mut().overlay(
|
||||||
&mut self.state,
|
&mut self.state,
|
||||||
|
|
@ -217,6 +221,16 @@ where
|
||||||
|
|
||||||
event_statuses.push(event_status);
|
event_statuses.push(event_status);
|
||||||
|
|
||||||
|
match (redraw_requested_at, shell.redraw_requested_at()) {
|
||||||
|
(None, Some(at)) => {
|
||||||
|
redraw_requested_at = Some(at);
|
||||||
|
}
|
||||||
|
(Some(current), Some(new)) if new < current => {
|
||||||
|
redraw_requested_at = Some(new);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
|
||||||
if shell.is_layout_invalid() {
|
if shell.is_layout_invalid() {
|
||||||
let _ = ManuallyDrop::into_inner(manual_overlay);
|
let _ = ManuallyDrop::into_inner(manual_overlay);
|
||||||
|
|
||||||
|
|
@ -244,7 +258,7 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
if shell.are_widgets_invalid() {
|
if shell.are_widgets_invalid() {
|
||||||
state = State::Outdated;
|
outdated = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -289,6 +303,16 @@ where
|
||||||
self.overlay = None;
|
self.overlay = None;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
match (redraw_requested_at, shell.redraw_requested_at()) {
|
||||||
|
(None, Some(at)) => {
|
||||||
|
redraw_requested_at = Some(at);
|
||||||
|
}
|
||||||
|
(Some(current), Some(new)) if new < current => {
|
||||||
|
redraw_requested_at = Some(new);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
|
||||||
shell.revalidate_layout(|| {
|
shell.revalidate_layout(|| {
|
||||||
self.base = renderer.layout(
|
self.base = renderer.layout(
|
||||||
&self.root,
|
&self.root,
|
||||||
|
|
@ -299,14 +323,23 @@ where
|
||||||
});
|
});
|
||||||
|
|
||||||
if shell.are_widgets_invalid() {
|
if shell.are_widgets_invalid() {
|
||||||
state = State::Outdated;
|
outdated = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
event_status.merge(overlay_status)
|
event_status.merge(overlay_status)
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
(state, event_statuses)
|
(
|
||||||
|
if outdated {
|
||||||
|
State::Outdated
|
||||||
|
} else {
|
||||||
|
State::Updated {
|
||||||
|
redraw_requested_at,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
event_statuses,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Draws the [`UserInterface`] with the provided [`Renderer`].
|
/// Draws the [`UserInterface`] with the provided [`Renderer`].
|
||||||
|
|
@ -559,5 +592,8 @@ pub enum State {
|
||||||
|
|
||||||
/// The [`UserInterface`] is up-to-date and can be reused without
|
/// The [`UserInterface`] is up-to-date and can be reused without
|
||||||
/// rebuilding.
|
/// rebuilding.
|
||||||
Updated,
|
Updated {
|
||||||
|
/// The [`Instant`] when a redraw should be performed.
|
||||||
|
redraw_requested_at: Option<Instant>,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,11 +22,14 @@ use crate::touch;
|
||||||
use crate::widget;
|
use crate::widget;
|
||||||
use crate::widget::operation::{self, Operation};
|
use crate::widget::operation::{self, Operation};
|
||||||
use crate::widget::tree::{self, Tree};
|
use crate::widget::tree::{self, Tree};
|
||||||
|
use crate::window;
|
||||||
use crate::{
|
use crate::{
|
||||||
Clipboard, Color, Command, Element, Layout, Length, Padding, Point,
|
Clipboard, Color, Command, Element, Layout, Length, Padding, Point,
|
||||||
Rectangle, Shell, Size, Vector, Widget,
|
Rectangle, Shell, Size, Vector, Widget,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use std::time::{Duration, Instant};
|
||||||
|
|
||||||
pub use iced_style::text_input::{Appearance, StyleSheet};
|
pub use iced_style::text_input::{Appearance, StyleSheet};
|
||||||
|
|
||||||
/// A field that can be filled with text.
|
/// A field that can be filled with text.
|
||||||
|
|
@ -425,7 +428,16 @@ where
|
||||||
let state = state();
|
let state = state();
|
||||||
let is_clicked = layout.bounds().contains(cursor_position);
|
let is_clicked = layout.bounds().contains(cursor_position);
|
||||||
|
|
||||||
state.is_focused = is_clicked;
|
state.is_focused = if is_clicked {
|
||||||
|
let now = Instant::now();
|
||||||
|
|
||||||
|
Some(Focus {
|
||||||
|
at: now,
|
||||||
|
last_draw: now,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
if is_clicked {
|
if is_clicked {
|
||||||
let text_layout = layout.children().next().unwrap();
|
let text_layout = layout.children().next().unwrap();
|
||||||
|
|
@ -541,26 +553,30 @@ where
|
||||||
Event::Keyboard(keyboard::Event::CharacterReceived(c)) => {
|
Event::Keyboard(keyboard::Event::CharacterReceived(c)) => {
|
||||||
let state = state();
|
let state = state();
|
||||||
|
|
||||||
if state.is_focused
|
if let Some(focus) = &mut state.is_focused {
|
||||||
&& state.is_pasting.is_none()
|
if state.is_pasting.is_none()
|
||||||
&& !state.keyboard_modifiers.command()
|
&& !state.keyboard_modifiers.command()
|
||||||
&& !c.is_control()
|
&& !c.is_control()
|
||||||
{
|
{
|
||||||
let mut editor = Editor::new(value, &mut state.cursor);
|
let mut editor = Editor::new(value, &mut state.cursor);
|
||||||
|
|
||||||
editor.insert(c);
|
editor.insert(c);
|
||||||
|
|
||||||
let message = (on_change)(editor.contents());
|
let message = (on_change)(editor.contents());
|
||||||
shell.publish(message);
|
shell.publish(message);
|
||||||
|
|
||||||
return event::Status::Captured;
|
focus.at = Instant::now();
|
||||||
|
|
||||||
|
return event::Status::Captured;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Event::Keyboard(keyboard::Event::KeyPressed { key_code, .. }) => {
|
Event::Keyboard(keyboard::Event::KeyPressed { key_code, .. }) => {
|
||||||
let state = state();
|
let state = state();
|
||||||
|
|
||||||
if state.is_focused {
|
if let Some(focus) = &mut state.is_focused {
|
||||||
let modifiers = state.keyboard_modifiers;
|
let modifiers = state.keyboard_modifiers;
|
||||||
|
focus.at = Instant::now();
|
||||||
|
|
||||||
match key_code {
|
match key_code {
|
||||||
keyboard::KeyCode::Enter
|
keyboard::KeyCode::Enter
|
||||||
|
|
@ -721,7 +737,7 @@ where
|
||||||
state.cursor.select_all(value);
|
state.cursor.select_all(value);
|
||||||
}
|
}
|
||||||
keyboard::KeyCode::Escape => {
|
keyboard::KeyCode::Escape => {
|
||||||
state.is_focused = false;
|
state.is_focused = None;
|
||||||
state.is_dragging = false;
|
state.is_dragging = false;
|
||||||
state.is_pasting = None;
|
state.is_pasting = None;
|
||||||
|
|
||||||
|
|
@ -742,7 +758,7 @@ where
|
||||||
Event::Keyboard(keyboard::Event::KeyReleased { key_code, .. }) => {
|
Event::Keyboard(keyboard::Event::KeyReleased { key_code, .. }) => {
|
||||||
let state = state();
|
let state = state();
|
||||||
|
|
||||||
if state.is_focused {
|
if state.is_focused.is_some() {
|
||||||
match key_code {
|
match key_code {
|
||||||
keyboard::KeyCode::V => {
|
keyboard::KeyCode::V => {
|
||||||
state.is_pasting = None;
|
state.is_pasting = None;
|
||||||
|
|
@ -765,6 +781,21 @@ where
|
||||||
|
|
||||||
state.keyboard_modifiers = modifiers;
|
state.keyboard_modifiers = modifiers;
|
||||||
}
|
}
|
||||||
|
Event::Window(window::Event::RedrawRequested(now)) => {
|
||||||
|
let state = state();
|
||||||
|
|
||||||
|
if let Some(focus) = &mut state.is_focused {
|
||||||
|
focus.last_draw = now;
|
||||||
|
|
||||||
|
let millis_until_redraw = CURSOR_BLINK_INTERVAL_MILLIS
|
||||||
|
- (now - focus.at).as_millis()
|
||||||
|
% CURSOR_BLINK_INTERVAL_MILLIS;
|
||||||
|
|
||||||
|
shell.request_redraw(
|
||||||
|
now + Duration::from_millis(millis_until_redraw as u64),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -820,7 +851,7 @@ pub fn draw<Renderer>(
|
||||||
let text = value.to_string();
|
let text = value.to_string();
|
||||||
let size = size.unwrap_or_else(|| renderer.default_size());
|
let size = size.unwrap_or_else(|| renderer.default_size());
|
||||||
|
|
||||||
let (cursor, offset) = if state.is_focused() {
|
let (cursor, offset) = if let Some(focus) = &state.is_focused {
|
||||||
match state.cursor.state(value) {
|
match state.cursor.state(value) {
|
||||||
cursor::State::Index(position) => {
|
cursor::State::Index(position) => {
|
||||||
let (text_value_width, offset) =
|
let (text_value_width, offset) =
|
||||||
|
|
@ -833,7 +864,13 @@ pub fn draw<Renderer>(
|
||||||
font.clone(),
|
font.clone(),
|
||||||
);
|
);
|
||||||
|
|
||||||
(
|
let is_cursor_visible = ((focus.last_draw - focus.at)
|
||||||
|
.as_millis()
|
||||||
|
/ CURSOR_BLINK_INTERVAL_MILLIS)
|
||||||
|
% 2
|
||||||
|
== 0;
|
||||||
|
|
||||||
|
let cursor = if is_cursor_visible {
|
||||||
Some((
|
Some((
|
||||||
renderer::Quad {
|
renderer::Quad {
|
||||||
bounds: Rectangle {
|
bounds: Rectangle {
|
||||||
|
|
@ -847,9 +884,12 @@ pub fn draw<Renderer>(
|
||||||
border_color: Color::TRANSPARENT,
|
border_color: Color::TRANSPARENT,
|
||||||
},
|
},
|
||||||
theme.value_color(style),
|
theme.value_color(style),
|
||||||
)),
|
))
|
||||||
offset,
|
} else {
|
||||||
)
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
(cursor, offset)
|
||||||
}
|
}
|
||||||
cursor::State::Selection { start, end } => {
|
cursor::State::Selection { start, end } => {
|
||||||
let left = start.min(end);
|
let left = start.min(end);
|
||||||
|
|
@ -958,7 +998,7 @@ pub fn mouse_interaction(
|
||||||
/// The state of a [`TextInput`].
|
/// The state of a [`TextInput`].
|
||||||
#[derive(Debug, Default, Clone)]
|
#[derive(Debug, Default, Clone)]
|
||||||
pub struct State {
|
pub struct State {
|
||||||
is_focused: bool,
|
is_focused: Option<Focus>,
|
||||||
is_dragging: bool,
|
is_dragging: bool,
|
||||||
is_pasting: Option<Value>,
|
is_pasting: Option<Value>,
|
||||||
last_click: Option<mouse::Click>,
|
last_click: Option<mouse::Click>,
|
||||||
|
|
@ -967,6 +1007,12 @@ pub struct State {
|
||||||
// TODO: Add stateful horizontal scrolling offset
|
// TODO: Add stateful horizontal scrolling offset
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
struct Focus {
|
||||||
|
at: Instant,
|
||||||
|
last_draw: Instant,
|
||||||
|
}
|
||||||
|
|
||||||
impl State {
|
impl State {
|
||||||
/// Creates a new [`State`], representing an unfocused [`TextInput`].
|
/// Creates a new [`State`], representing an unfocused [`TextInput`].
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
|
|
@ -976,7 +1022,7 @@ impl State {
|
||||||
/// Creates a new [`State`], representing a focused [`TextInput`].
|
/// Creates a new [`State`], representing a focused [`TextInput`].
|
||||||
pub fn focused() -> Self {
|
pub fn focused() -> Self {
|
||||||
Self {
|
Self {
|
||||||
is_focused: true,
|
is_focused: None,
|
||||||
is_dragging: false,
|
is_dragging: false,
|
||||||
is_pasting: None,
|
is_pasting: None,
|
||||||
last_click: None,
|
last_click: None,
|
||||||
|
|
@ -987,7 +1033,7 @@ impl State {
|
||||||
|
|
||||||
/// Returns whether the [`TextInput`] is currently focused or not.
|
/// Returns whether the [`TextInput`] is currently focused or not.
|
||||||
pub fn is_focused(&self) -> bool {
|
pub fn is_focused(&self) -> bool {
|
||||||
self.is_focused
|
self.is_focused.is_some()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the [`Cursor`] of the [`TextInput`].
|
/// Returns the [`Cursor`] of the [`TextInput`].
|
||||||
|
|
@ -997,13 +1043,19 @@ impl State {
|
||||||
|
|
||||||
/// Focuses the [`TextInput`].
|
/// Focuses the [`TextInput`].
|
||||||
pub fn focus(&mut self) {
|
pub fn focus(&mut self) {
|
||||||
self.is_focused = true;
|
let now = Instant::now();
|
||||||
|
|
||||||
|
self.is_focused = Some(Focus {
|
||||||
|
at: now,
|
||||||
|
last_draw: now,
|
||||||
|
});
|
||||||
|
|
||||||
self.move_cursor_to_end();
|
self.move_cursor_to_end();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Unfocuses the [`TextInput`].
|
/// Unfocuses the [`TextInput`].
|
||||||
pub fn unfocus(&mut self) {
|
pub fn unfocus(&mut self) {
|
||||||
self.is_focused = false;
|
self.is_focused = None;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Moves the [`Cursor`] of the [`TextInput`] to the front of the input text.
|
/// Moves the [`Cursor`] of the [`TextInput`] to the front of the input text.
|
||||||
|
|
@ -1156,3 +1208,5 @@ where
|
||||||
)
|
)
|
||||||
.map(text::Hit::cursor)
|
.map(text::Hit::cursor)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const CURSOR_BLINK_INTERVAL_MILLIS: u128 = 500;
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
use std::time::Instant;
|
||||||
|
|
||||||
/// A window-related event.
|
/// A window-related event.
|
||||||
#[derive(PartialEq, Eq, Clone, Debug)]
|
#[derive(PartialEq, Eq, Clone, Debug)]
|
||||||
|
|
@ -19,6 +20,11 @@ pub enum Event {
|
||||||
height: u32,
|
height: u32,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/// A window redraw was requested.
|
||||||
|
///
|
||||||
|
/// The [`Instant`] contains the current time.
|
||||||
|
RedrawRequested(Instant),
|
||||||
|
|
||||||
/// The user has requested for the window to close.
|
/// The user has requested for the window to close.
|
||||||
///
|
///
|
||||||
/// Usually, you will want to terminate the execution whenever this event
|
/// Usually, you will want to terminate the execution whenever this event
|
||||||
|
|
|
||||||
|
|
@ -180,13 +180,6 @@ pub trait Application: Sized {
|
||||||
1.0
|
1.0
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns whether the [`Application`] should be terminated.
|
|
||||||
///
|
|
||||||
/// By default, it returns `false`.
|
|
||||||
fn should_exit(&self) -> bool {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Runs the [`Application`].
|
/// Runs the [`Application`].
|
||||||
///
|
///
|
||||||
/// On native platforms, this method will take control of the current thread
|
/// On native platforms, this method will take control of the current thread
|
||||||
|
|
|
||||||
|
|
@ -140,13 +140,6 @@ pub trait Sandbox {
|
||||||
1.0
|
1.0
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns whether the [`Sandbox`] should be terminated.
|
|
||||||
///
|
|
||||||
/// By default, it returns `false`.
|
|
||||||
fn should_exit(&self) -> bool {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Runs the [`Sandbox`].
|
/// Runs the [`Sandbox`].
|
||||||
///
|
///
|
||||||
/// On native platforms, this method will take control of the current thread
|
/// On native platforms, this method will take control of the current thread
|
||||||
|
|
@ -203,8 +196,4 @@ where
|
||||||
fn scale_factor(&self) -> f64 {
|
fn scale_factor(&self) -> f64 {
|
||||||
T::scale_factor(self)
|
T::scale_factor(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn should_exit(&self) -> bool {
|
|
||||||
T::should_exit(self)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ use crate::mouse;
|
||||||
use crate::renderer;
|
use crate::renderer;
|
||||||
use crate::widget::operation;
|
use crate::widget::operation;
|
||||||
use crate::{
|
use crate::{
|
||||||
Command, Debug, Error, Executor, Proxy, Runtime, Settings, Size,
|
Command, Debug, Error, Event, Executor, Proxy, Runtime, Settings, Size,
|
||||||
Subscription,
|
Subscription,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -25,6 +25,7 @@ use iced_native::user_interface::{self, UserInterface};
|
||||||
pub use iced_native::application::{Appearance, StyleSheet};
|
pub use iced_native::application::{Appearance, StyleSheet};
|
||||||
|
|
||||||
use std::mem::ManuallyDrop;
|
use std::mem::ManuallyDrop;
|
||||||
|
use std::time::Instant;
|
||||||
|
|
||||||
#[cfg(feature = "trace")]
|
#[cfg(feature = "trace")]
|
||||||
pub use profiler::Profiler;
|
pub use profiler::Profiler;
|
||||||
|
|
@ -186,7 +187,8 @@ where
|
||||||
|
|
||||||
let (compositor, renderer) = C::new(compositor_settings, Some(&window))?;
|
let (compositor, renderer) = C::new(compositor_settings, Some(&window))?;
|
||||||
|
|
||||||
let (mut sender, receiver) = mpsc::unbounded();
|
let (mut event_sender, event_receiver) = mpsc::unbounded();
|
||||||
|
let (control_sender, mut control_receiver) = mpsc::unbounded();
|
||||||
|
|
||||||
let mut instance = Box::pin({
|
let mut instance = Box::pin({
|
||||||
let run_instance = run_instance::<A, E, C>(
|
let run_instance = run_instance::<A, E, C>(
|
||||||
|
|
@ -196,7 +198,8 @@ where
|
||||||
runtime,
|
runtime,
|
||||||
proxy,
|
proxy,
|
||||||
debug,
|
debug,
|
||||||
receiver,
|
event_receiver,
|
||||||
|
control_sender,
|
||||||
init_command,
|
init_command,
|
||||||
window,
|
window,
|
||||||
settings.exit_on_close_request,
|
settings.exit_on_close_request,
|
||||||
|
|
@ -234,13 +237,19 @@ where
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(event) = event {
|
if let Some(event) = event {
|
||||||
sender.start_send(event).expect("Send event");
|
event_sender.start_send(event).expect("Send event");
|
||||||
|
|
||||||
let poll = instance.as_mut().poll(&mut context);
|
let poll = instance.as_mut().poll(&mut context);
|
||||||
|
|
||||||
*control_flow = match poll {
|
match poll {
|
||||||
task::Poll::Pending => ControlFlow::Wait,
|
task::Poll::Pending => {
|
||||||
task::Poll::Ready(_) => ControlFlow::Exit,
|
if let Ok(Some(flow)) = control_receiver.try_next() {
|
||||||
|
*control_flow = flow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
task::Poll::Ready(_) => {
|
||||||
|
*control_flow = ControlFlow::Exit;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
@ -253,7 +262,10 @@ async fn run_instance<A, E, C>(
|
||||||
mut runtime: Runtime<E, Proxy<A::Message>, A::Message>,
|
mut runtime: Runtime<E, Proxy<A::Message>, A::Message>,
|
||||||
mut proxy: winit::event_loop::EventLoopProxy<A::Message>,
|
mut proxy: winit::event_loop::EventLoopProxy<A::Message>,
|
||||||
mut debug: Debug,
|
mut debug: Debug,
|
||||||
mut receiver: mpsc::UnboundedReceiver<winit::event::Event<'_, A::Message>>,
|
mut event_receiver: mpsc::UnboundedReceiver<
|
||||||
|
winit::event::Event<'_, A::Message>,
|
||||||
|
>,
|
||||||
|
mut control_sender: mpsc::UnboundedSender<winit::event_loop::ControlFlow>,
|
||||||
init_command: Command<A::Message>,
|
init_command: Command<A::Message>,
|
||||||
window: winit::window::Window,
|
window: winit::window::Window,
|
||||||
exit_on_close_request: bool,
|
exit_on_close_request: bool,
|
||||||
|
|
@ -265,6 +277,7 @@ async fn run_instance<A, E, C>(
|
||||||
{
|
{
|
||||||
use iced_futures::futures::stream::StreamExt;
|
use iced_futures::futures::stream::StreamExt;
|
||||||
use winit::event;
|
use winit::event;
|
||||||
|
use winit::event_loop::ControlFlow;
|
||||||
|
|
||||||
let mut clipboard = Clipboard::connect(&window);
|
let mut clipboard = Clipboard::connect(&window);
|
||||||
let mut cache = user_interface::Cache::default();
|
let mut cache = user_interface::Cache::default();
|
||||||
|
|
@ -309,13 +322,21 @@ async fn run_instance<A, E, C>(
|
||||||
let mut mouse_interaction = mouse::Interaction::default();
|
let mut mouse_interaction = mouse::Interaction::default();
|
||||||
let mut events = Vec::new();
|
let mut events = Vec::new();
|
||||||
let mut messages = Vec::new();
|
let mut messages = Vec::new();
|
||||||
|
let mut redraw_pending = false;
|
||||||
|
|
||||||
debug.startup_finished();
|
debug.startup_finished();
|
||||||
|
|
||||||
while let Some(event) = receiver.next().await {
|
while let Some(event) = event_receiver.next().await {
|
||||||
match event {
|
match event {
|
||||||
|
event::Event::NewEvents(start_cause) => {
|
||||||
|
redraw_pending = matches!(
|
||||||
|
start_cause,
|
||||||
|
event::StartCause::Init
|
||||||
|
| event::StartCause::ResumeTimeReached { .. }
|
||||||
|
);
|
||||||
|
}
|
||||||
event::Event::MainEventsCleared => {
|
event::Event::MainEventsCleared => {
|
||||||
if events.is_empty() && messages.is_empty() {
|
if !redraw_pending && events.is_empty() && messages.is_empty() {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -338,7 +359,7 @@ async fn run_instance<A, E, C>(
|
||||||
if !messages.is_empty()
|
if !messages.is_empty()
|
||||||
|| matches!(
|
|| matches!(
|
||||||
interface_state,
|
interface_state,
|
||||||
user_interface::State::Outdated,
|
user_interface::State::Outdated
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
let mut cache =
|
let mut cache =
|
||||||
|
|
@ -376,6 +397,24 @@ async fn run_instance<A, E, C>(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Avoid redrawing all the time by forcing widgets to
|
||||||
|
// request redraws on state changes
|
||||||
|
//
|
||||||
|
// Then, we can use the `interface_state` here to decide whether
|
||||||
|
// if a redraw is needed right away, or simply wait until a
|
||||||
|
// specific time.
|
||||||
|
let redraw_event = Event::Window(
|
||||||
|
crate::window::Event::RedrawRequested(Instant::now()),
|
||||||
|
);
|
||||||
|
|
||||||
|
let (interface_state, _) = user_interface.update(
|
||||||
|
&[redraw_event.clone()],
|
||||||
|
state.cursor_position(),
|
||||||
|
&mut renderer,
|
||||||
|
&mut clipboard,
|
||||||
|
&mut messages,
|
||||||
|
);
|
||||||
|
|
||||||
debug.draw_started();
|
debug.draw_started();
|
||||||
let new_mouse_interaction = user_interface.draw(
|
let new_mouse_interaction = user_interface.draw(
|
||||||
&mut renderer,
|
&mut renderer,
|
||||||
|
|
@ -396,6 +435,17 @@ async fn run_instance<A, E, C>(
|
||||||
}
|
}
|
||||||
|
|
||||||
window.request_redraw();
|
window.request_redraw();
|
||||||
|
runtime
|
||||||
|
.broadcast((redraw_event, crate::event::Status::Ignored));
|
||||||
|
|
||||||
|
let _ = control_sender.start_send(match interface_state {
|
||||||
|
user_interface::State::Updated {
|
||||||
|
redraw_requested_at: Some(at),
|
||||||
|
} => ControlFlow::WaitUntil(at),
|
||||||
|
_ => ControlFlow::Wait,
|
||||||
|
});
|
||||||
|
|
||||||
|
redraw_pending = false;
|
||||||
}
|
}
|
||||||
event::Event::PlatformSpecific(event::PlatformSpecific::MacOS(
|
event::Event::PlatformSpecific(event::PlatformSpecific::MacOS(
|
||||||
event::MacOS::ReceivedUrl(url),
|
event::MacOS::ReceivedUrl(url),
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue