Merge remote-tracking branch 'origin/master' into feat/multi-window-support
# Conflicts: # examples/events/src/main.rs # glutin/src/application.rs # native/src/window.rs # winit/src/window.rs
This commit is contained in:
commit
70d487ba20
57 changed files with 815 additions and 446 deletions
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "iced_native"
|
||||
version = "0.7.0"
|
||||
version = "0.8.0"
|
||||
authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]
|
||||
edition = "2021"
|
||||
description = "A renderer-agnostic library for native GUIs"
|
||||
|
|
@ -16,7 +16,7 @@ unicode-segmentation = "1.6"
|
|||
num-traits = "0.2"
|
||||
|
||||
[dependencies.iced_core]
|
||||
version = "0.6"
|
||||
version = "0.7"
|
||||
path = "../core"
|
||||
|
||||
[dependencies.iced_futures]
|
||||
|
|
@ -25,5 +25,5 @@ path = "../futures"
|
|||
features = ["thread-pool"]
|
||||
|
||||
[dependencies.iced_style]
|
||||
version = "0.5.1"
|
||||
version = "0.6.0"
|
||||
path = "../style"
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ To achieve this, it introduces a bunch of reusable interfaces:
|
|||
Add `iced_native` as a dependency in your `Cargo.toml`:
|
||||
|
||||
```toml
|
||||
iced_native = "0.4"
|
||||
iced_native = "0.8"
|
||||
```
|
||||
|
||||
__Iced moves fast and the `master` branch can contain breaking changes!__ If
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ use crate::{
|
|||
Clipboard, Color, Layout, Length, Point, Rectangle, Shell, Widget,
|
||||
};
|
||||
|
||||
use std::any::Any;
|
||||
use std::borrow::Borrow;
|
||||
|
||||
/// A generic [`Widget`].
|
||||
|
|
@ -333,6 +334,10 @@ where
|
|||
) {
|
||||
self.operation.text_input(state, id);
|
||||
}
|
||||
|
||||
fn custom(&mut self, state: &mut dyn Any, id: Option<&widget::Id>) {
|
||||
self.operation.custom(state, id);
|
||||
}
|
||||
}
|
||||
|
||||
self.widget.operate(
|
||||
|
|
|
|||
|
|
@ -23,8 +23,8 @@
|
|||
//! - Build a new renderer, see the [renderer] module.
|
||||
//! - Build a custom widget, start at the [`Widget`] trait.
|
||||
//!
|
||||
//! [`iced_core`]: https://github.com/iced-rs/iced/tree/0.6/core
|
||||
//! [`iced_winit`]: https://github.com/iced-rs/iced/tree/0.6/winit
|
||||
//! [`iced_core`]: https://github.com/iced-rs/iced/tree/0.7/core
|
||||
//! [`iced_winit`]: https://github.com/iced-rs/iced/tree/0.7/winit
|
||||
//! [`druid`]: https://github.com/xi-editor/druid
|
||||
//! [`raw-window-handle`]: https://github.com/rust-windowing/raw-window-handle
|
||||
//! [renderer]: crate::renderer
|
||||
|
|
|
|||
|
|
@ -7,6 +7,8 @@ use crate::renderer;
|
|||
use crate::widget;
|
||||
use crate::{Clipboard, Layout, Point, Rectangle, Shell, Size, Vector};
|
||||
|
||||
use std::any::Any;
|
||||
|
||||
/// A generic [`Overlay`].
|
||||
#[allow(missing_debug_implementations)]
|
||||
pub struct Element<'a, Message, Renderer> {
|
||||
|
|
@ -188,6 +190,10 @@ where
|
|||
) {
|
||||
self.operation.text_input(state, id)
|
||||
}
|
||||
|
||||
fn custom(&mut self, state: &mut dyn Any, id: Option<&widget::Id>) {
|
||||
self.operation.custom(state, id);
|
||||
}
|
||||
}
|
||||
|
||||
self.content
|
||||
|
|
|
|||
|
|
@ -36,11 +36,11 @@ pub trait Renderer: Sized {
|
|||
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`].
|
||||
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.
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
use crate::window;
|
||||
|
||||
/// A connection to the state of a shell.
|
||||
///
|
||||
/// A [`Widget`] can leverage a [`Shell`] to trigger changes in an application,
|
||||
|
|
@ -7,6 +9,7 @@
|
|||
#[derive(Debug)]
|
||||
pub struct Shell<'a, Message> {
|
||||
messages: &'a mut Vec<Message>,
|
||||
redraw_request: Option<window::RedrawRequest>,
|
||||
is_layout_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 {
|
||||
Self {
|
||||
messages,
|
||||
redraw_request: None,
|
||||
is_layout_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, request: window::RedrawRequest) {
|
||||
match self.redraw_request {
|
||||
None => {
|
||||
self.redraw_request = Some(request);
|
||||
}
|
||||
Some(current) if request < current => {
|
||||
self.redraw_request = Some(request);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the requested [`Instant`] a redraw should happen, if any.
|
||||
pub fn redraw_request(&self) -> Option<window::RedrawRequest> {
|
||||
self.redraw_request
|
||||
}
|
||||
|
||||
/// 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
|
||||
/// process.
|
||||
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.
|
||||
pub fn is_layout_invalid(&self) -> bool {
|
||||
self.is_layout_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;
|
||||
/// Returns whether the widgets of the current application have been
|
||||
/// invalidated.
|
||||
pub fn are_widgets_invalid(&self) -> bool {
|
||||
self.are_widgets_invalid
|
||||
}
|
||||
|
||||
/// 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) {
|
||||
self.messages.extend(other.messages.drain(..).map(f));
|
||||
|
||||
if let Some(at) = other.redraw_request {
|
||||
self.request_redraw(at);
|
||||
}
|
||||
|
||||
self.is_layout_invalid =
|
||||
self.is_layout_invalid || other.is_layout_invalid;
|
||||
|
||||
self.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.
|
||||
use crate::event::{self, Event};
|
||||
use crate::window;
|
||||
use crate::Hasher;
|
||||
|
||||
use iced_futures::futures::{self, Future, Stream};
|
||||
|
|
@ -33,7 +34,7 @@ pub type Tracker =
|
|||
|
||||
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
|
||||
/// not captured by any widget.
|
||||
|
|
@ -58,8 +59,36 @@ pub fn events_with<Message>(
|
|||
where
|
||||
Message: 'static + MaybeSend,
|
||||
{
|
||||
#[derive(Hash)]
|
||||
struct EventsWith;
|
||||
|
||||
Subscription::from_recipe(Runner {
|
||||
id: f,
|
||||
id: (EventsWith, f),
|
||||
spawn: move |events| {
|
||||
use futures::future;
|
||||
use futures::stream::StreamExt;
|
||||
|
||||
events.filter_map(move |(event, status)| {
|
||||
future::ready(match event {
|
||||
Event::Window(window::Event::RedrawRequested(_)) => None,
|
||||
_ => f(event, status),
|
||||
})
|
||||
})
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn raw_events<Message>(
|
||||
f: fn(Event, event::Status) -> Option<Message>,
|
||||
) -> Subscription<Message>
|
||||
where
|
||||
Message: 'static + MaybeSend,
|
||||
{
|
||||
#[derive(Hash)]
|
||||
struct RawEvents;
|
||||
|
||||
Subscription::from_recipe(Runner {
|
||||
id: (RawEvents, f),
|
||||
spawn: move |events| {
|
||||
use futures::future;
|
||||
use futures::stream::StreamExt;
|
||||
|
|
@ -155,7 +184,7 @@ where
|
|||
/// Check out the [`websocket`] example, which showcases this pattern to maintain a WebSocket
|
||||
/// connection open.
|
||||
///
|
||||
/// [`websocket`]: https://github.com/iced-rs/iced/tree/0.6/examples/websocket
|
||||
/// [`websocket`]: https://github.com/iced-rs/iced/tree/0.7/examples/websocket
|
||||
pub fn unfold<I, T, Fut, Message>(
|
||||
id: I,
|
||||
initial: T,
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ use crate::layout;
|
|||
use crate::mouse;
|
||||
use crate::renderer;
|
||||
use crate::widget;
|
||||
use crate::window;
|
||||
use crate::{Clipboard, Element, Layout, Point, Rectangle, Shell, Size};
|
||||
|
||||
/// A set of interactive graphical elements with a specific [`Layout`].
|
||||
|
|
@ -18,8 +19,8 @@ use crate::{Clipboard, Element, Layout, Point, Rectangle, Shell, Size};
|
|||
/// The [`integration_opengl`] & [`integration_wgpu`] examples use a
|
||||
/// [`UserInterface`] to integrate Iced in an existing graphical application.
|
||||
///
|
||||
/// [`integration_opengl`]: https://github.com/iced-rs/iced/tree/0.6/examples/integration_opengl
|
||||
/// [`integration_wgpu`]: https://github.com/iced-rs/iced/tree/0.6/examples/integration_wgpu
|
||||
/// [`integration_opengl`]: https://github.com/iced-rs/iced/tree/0.7/examples/integration_opengl
|
||||
/// [`integration_wgpu`]: https://github.com/iced-rs/iced/tree/0.7/examples/integration_wgpu
|
||||
#[allow(missing_debug_implementations)]
|
||||
pub struct UserInterface<'a, Message, Renderer> {
|
||||
root: Element<'a, Message, Renderer>,
|
||||
|
|
@ -188,7 +189,9 @@ where
|
|||
) -> (State, Vec<event::Status>) {
|
||||
use std::mem::ManuallyDrop;
|
||||
|
||||
let mut state = State::Updated;
|
||||
let mut outdated = false;
|
||||
let mut redraw_request = None;
|
||||
|
||||
let mut manual_overlay =
|
||||
ManuallyDrop::new(self.root.as_widget_mut().overlay(
|
||||
&mut self.state,
|
||||
|
|
@ -217,6 +220,16 @@ where
|
|||
|
||||
event_statuses.push(event_status);
|
||||
|
||||
match (redraw_request, shell.redraw_request()) {
|
||||
(None, Some(at)) => {
|
||||
redraw_request = Some(at);
|
||||
}
|
||||
(Some(current), Some(new)) if new < current => {
|
||||
redraw_request = Some(new);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
if shell.is_layout_invalid() {
|
||||
let _ = ManuallyDrop::into_inner(manual_overlay);
|
||||
|
||||
|
|
@ -244,7 +257,7 @@ where
|
|||
}
|
||||
|
||||
if shell.are_widgets_invalid() {
|
||||
state = State::Outdated;
|
||||
outdated = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -289,6 +302,16 @@ where
|
|||
self.overlay = None;
|
||||
}
|
||||
|
||||
match (redraw_request, shell.redraw_request()) {
|
||||
(None, Some(at)) => {
|
||||
redraw_request = Some(at);
|
||||
}
|
||||
(Some(current), Some(new)) if new < current => {
|
||||
redraw_request = Some(new);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
shell.revalidate_layout(|| {
|
||||
self.base = renderer.layout(
|
||||
&self.root,
|
||||
|
|
@ -299,14 +322,21 @@ where
|
|||
});
|
||||
|
||||
if shell.are_widgets_invalid() {
|
||||
state = State::Outdated;
|
||||
outdated = true;
|
||||
}
|
||||
|
||||
event_status.merge(overlay_status)
|
||||
})
|
||||
.collect();
|
||||
|
||||
(state, event_statuses)
|
||||
(
|
||||
if outdated {
|
||||
State::Outdated
|
||||
} else {
|
||||
State::Updated { redraw_request }
|
||||
},
|
||||
event_statuses,
|
||||
)
|
||||
}
|
||||
|
||||
/// Draws the [`UserInterface`] with the provided [`Renderer`].
|
||||
|
|
@ -559,5 +589,8 @@ pub enum State {
|
|||
|
||||
/// The [`UserInterface`] is up-to-date and can be reused without
|
||||
/// rebuilding.
|
||||
Updated,
|
||||
Updated {
|
||||
/// The [`Instant`] when a redraw should be performed.
|
||||
redraw_request: Option<window::RedrawRequest>,
|
||||
},
|
||||
}
|
||||
|
|
|
|||
|
|
@ -110,12 +110,12 @@ use crate::{Clipboard, Layout, Length, Point, Rectangle, Shell};
|
|||
/// - [`geometry`], a custom widget showcasing how to draw geometry with the
|
||||
/// `Mesh2D` primitive in [`iced_wgpu`].
|
||||
///
|
||||
/// [examples]: https://github.com/iced-rs/iced/tree/0.6/examples
|
||||
/// [`bezier_tool`]: https://github.com/iced-rs/iced/tree/0.6/examples/bezier_tool
|
||||
/// [`custom_widget`]: https://github.com/iced-rs/iced/tree/0.6/examples/custom_widget
|
||||
/// [`geometry`]: https://github.com/iced-rs/iced/tree/0.6/examples/geometry
|
||||
/// [examples]: https://github.com/iced-rs/iced/tree/0.7/examples
|
||||
/// [`bezier_tool`]: https://github.com/iced-rs/iced/tree/0.7/examples/bezier_tool
|
||||
/// [`custom_widget`]: https://github.com/iced-rs/iced/tree/0.7/examples/custom_widget
|
||||
/// [`geometry`]: https://github.com/iced-rs/iced/tree/0.7/examples/geometry
|
||||
/// [`lyon`]: https://github.com/nical/lyon
|
||||
/// [`iced_wgpu`]: https://github.com/iced-rs/iced/tree/0.6/wgpu
|
||||
/// [`iced_wgpu`]: https://github.com/iced-rs/iced/tree/0.7/wgpu
|
||||
pub trait Widget<Message, Renderer>
|
||||
where
|
||||
Renderer: crate::Renderer,
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ use crate::widget::Id;
|
|||
|
||||
use iced_futures::MaybeSend;
|
||||
|
||||
use std::any::Any;
|
||||
use std::rc::Rc;
|
||||
|
||||
/// An operation to be performed on the widget tree.
|
||||
|
|
@ -84,6 +85,10 @@ where
|
|||
) {
|
||||
self.operation.focusable(state, id);
|
||||
}
|
||||
|
||||
fn custom(&mut self, state: &mut dyn Any, id: Option<&Id>) {
|
||||
self.operation.custom(state, id);
|
||||
}
|
||||
}
|
||||
|
||||
let Self { operation, .. } = self;
|
||||
|
|
@ -118,6 +123,10 @@ where
|
|||
self.operation.text_input(state, id);
|
||||
}
|
||||
|
||||
fn custom(&mut self, state: &mut dyn Any, id: Option<&Id>) {
|
||||
self.operation.custom(state, id);
|
||||
}
|
||||
|
||||
fn finish(&self) -> operation::Outcome<B> {
|
||||
match self.operation.finish() {
|
||||
operation::Outcome::None => operation::Outcome::None,
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ pub use text_input::TextInput;
|
|||
|
||||
use crate::widget::Id;
|
||||
|
||||
use std::any::Any;
|
||||
use std::fmt;
|
||||
|
||||
/// A piece of logic that can traverse the widget tree of an application in
|
||||
|
|
@ -33,6 +34,9 @@ pub trait Operation<T> {
|
|||
/// Operates on a widget that has text input.
|
||||
fn text_input(&mut self, _state: &mut dyn TextInput, _id: Option<&Id>) {}
|
||||
|
||||
/// Operates on a custom widget with some state.
|
||||
fn custom(&mut self, _state: &mut dyn Any, _id: Option<&Id>) {}
|
||||
|
||||
/// Finishes the [`Operation`] and returns its [`Outcome`].
|
||||
fn finish(&self) -> Outcome<T> {
|
||||
Outcome::None
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
//! The [`pane_grid` example] showcases how to use a [`PaneGrid`] with resizing,
|
||||
//! drag and drop, and hotkey support.
|
||||
//!
|
||||
//! [`pane_grid` example]: https://github.com/iced-rs/iced/tree/0.6/examples/pane_grid
|
||||
//! [`pane_grid` example]: https://github.com/iced-rs/iced/tree/0.7/examples/pane_grid
|
||||
mod axis;
|
||||
mod configuration;
|
||||
mod content;
|
||||
|
|
|
|||
|
|
@ -18,10 +18,12 @@ use crate::layout;
|
|||
use crate::mouse::{self, click};
|
||||
use crate::renderer;
|
||||
use crate::text::{self, Text};
|
||||
use crate::time::{Duration, Instant};
|
||||
use crate::touch;
|
||||
use crate::widget;
|
||||
use crate::widget::operation::{self, Operation};
|
||||
use crate::widget::tree::{self, Tree};
|
||||
use crate::window;
|
||||
use crate::{
|
||||
Clipboard, Color, Command, Element, Layout, Length, Padding, Point,
|
||||
Rectangle, Shell, Size, Vector, Widget,
|
||||
|
|
@ -425,7 +427,18 @@ where
|
|||
let state = state();
|
||||
let is_clicked = layout.bounds().contains(cursor_position);
|
||||
|
||||
state.is_focused = is_clicked;
|
||||
state.is_focused = if is_clicked {
|
||||
state.is_focused.or_else(|| {
|
||||
let now = Instant::now();
|
||||
|
||||
Some(Focus {
|
||||
updated_at: now,
|
||||
now,
|
||||
})
|
||||
})
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
if is_clicked {
|
||||
let text_layout = layout.children().next().unwrap();
|
||||
|
|
@ -541,26 +554,30 @@ where
|
|||
Event::Keyboard(keyboard::Event::CharacterReceived(c)) => {
|
||||
let state = state();
|
||||
|
||||
if state.is_focused
|
||||
&& state.is_pasting.is_none()
|
||||
&& !state.keyboard_modifiers.command()
|
||||
&& !c.is_control()
|
||||
{
|
||||
let mut editor = Editor::new(value, &mut state.cursor);
|
||||
if let Some(focus) = &mut state.is_focused {
|
||||
if state.is_pasting.is_none()
|
||||
&& !state.keyboard_modifiers.command()
|
||||
&& !c.is_control()
|
||||
{
|
||||
let mut editor = Editor::new(value, &mut state.cursor);
|
||||
|
||||
editor.insert(c);
|
||||
editor.insert(c);
|
||||
|
||||
let message = (on_change)(editor.contents());
|
||||
shell.publish(message);
|
||||
let message = (on_change)(editor.contents());
|
||||
shell.publish(message);
|
||||
|
||||
return event::Status::Captured;
|
||||
focus.updated_at = Instant::now();
|
||||
|
||||
return event::Status::Captured;
|
||||
}
|
||||
}
|
||||
}
|
||||
Event::Keyboard(keyboard::Event::KeyPressed { key_code, .. }) => {
|
||||
let state = state();
|
||||
|
||||
if state.is_focused {
|
||||
if let Some(focus) = &mut state.is_focused {
|
||||
let modifiers = state.keyboard_modifiers;
|
||||
focus.updated_at = Instant::now();
|
||||
|
||||
match key_code {
|
||||
keyboard::KeyCode::Enter
|
||||
|
|
@ -721,7 +738,7 @@ where
|
|||
state.cursor.select_all(value);
|
||||
}
|
||||
keyboard::KeyCode::Escape => {
|
||||
state.is_focused = false;
|
||||
state.is_focused = None;
|
||||
state.is_dragging = false;
|
||||
state.is_pasting = None;
|
||||
|
||||
|
|
@ -742,7 +759,7 @@ where
|
|||
Event::Keyboard(keyboard::Event::KeyReleased { key_code, .. }) => {
|
||||
let state = state();
|
||||
|
||||
if state.is_focused {
|
||||
if state.is_focused.is_some() {
|
||||
match key_code {
|
||||
keyboard::KeyCode::V => {
|
||||
state.is_pasting = None;
|
||||
|
|
@ -765,6 +782,21 @@ where
|
|||
|
||||
state.keyboard_modifiers = modifiers;
|
||||
}
|
||||
Event::Window(window::Event::RedrawRequested(now)) => {
|
||||
let state = state();
|
||||
|
||||
if let Some(focus) = &mut state.is_focused {
|
||||
focus.now = now;
|
||||
|
||||
let millis_until_redraw = CURSOR_BLINK_INTERVAL_MILLIS
|
||||
- (now - focus.updated_at).as_millis()
|
||||
% CURSOR_BLINK_INTERVAL_MILLIS;
|
||||
|
||||
shell.request_redraw(window::RedrawRequest::At(
|
||||
now + Duration::from_millis(millis_until_redraw as u64),
|
||||
));
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
|
|
@ -820,7 +852,7 @@ pub fn draw<Renderer>(
|
|||
let text = value.to_string();
|
||||
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) {
|
||||
cursor::State::Index(position) => {
|
||||
let (text_value_width, offset) =
|
||||
|
|
@ -833,7 +865,13 @@ pub fn draw<Renderer>(
|
|||
font.clone(),
|
||||
);
|
||||
|
||||
(
|
||||
let is_cursor_visible = ((focus.now - focus.updated_at)
|
||||
.as_millis()
|
||||
/ CURSOR_BLINK_INTERVAL_MILLIS)
|
||||
% 2
|
||||
== 0;
|
||||
|
||||
let cursor = if is_cursor_visible {
|
||||
Some((
|
||||
renderer::Quad {
|
||||
bounds: Rectangle {
|
||||
|
|
@ -847,9 +885,12 @@ pub fn draw<Renderer>(
|
|||
border_color: Color::TRANSPARENT,
|
||||
},
|
||||
theme.value_color(style),
|
||||
)),
|
||||
offset,
|
||||
)
|
||||
))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
(cursor, offset)
|
||||
}
|
||||
cursor::State::Selection { start, end } => {
|
||||
let left = start.min(end);
|
||||
|
|
@ -958,7 +999,7 @@ pub fn mouse_interaction(
|
|||
/// The state of a [`TextInput`].
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct State {
|
||||
is_focused: bool,
|
||||
is_focused: Option<Focus>,
|
||||
is_dragging: bool,
|
||||
is_pasting: Option<Value>,
|
||||
last_click: Option<mouse::Click>,
|
||||
|
|
@ -967,6 +1008,12 @@ pub struct State {
|
|||
// TODO: Add stateful horizontal scrolling offset
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
struct Focus {
|
||||
updated_at: Instant,
|
||||
now: Instant,
|
||||
}
|
||||
|
||||
impl State {
|
||||
/// Creates a new [`State`], representing an unfocused [`TextInput`].
|
||||
pub fn new() -> Self {
|
||||
|
|
@ -976,7 +1023,7 @@ impl State {
|
|||
/// Creates a new [`State`], representing a focused [`TextInput`].
|
||||
pub fn focused() -> Self {
|
||||
Self {
|
||||
is_focused: true,
|
||||
is_focused: None,
|
||||
is_dragging: false,
|
||||
is_pasting: None,
|
||||
last_click: None,
|
||||
|
|
@ -987,7 +1034,7 @@ impl State {
|
|||
|
||||
/// Returns whether the [`TextInput`] is currently focused or not.
|
||||
pub fn is_focused(&self) -> bool {
|
||||
self.is_focused
|
||||
self.is_focused.is_some()
|
||||
}
|
||||
|
||||
/// Returns the [`Cursor`] of the [`TextInput`].
|
||||
|
|
@ -997,13 +1044,19 @@ impl State {
|
|||
|
||||
/// Focuses the [`TextInput`].
|
||||
pub fn focus(&mut self) {
|
||||
self.is_focused = true;
|
||||
let now = Instant::now();
|
||||
|
||||
self.is_focused = Some(Focus {
|
||||
updated_at: now,
|
||||
now,
|
||||
});
|
||||
|
||||
self.move_cursor_to_end();
|
||||
}
|
||||
|
||||
/// Unfocuses the [`TextInput`].
|
||||
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.
|
||||
|
|
@ -1156,3 +1209,5 @@ where
|
|||
)
|
||||
.map(text::Hit::cursor)
|
||||
}
|
||||
|
||||
const CURSOR_BLINK_INTERVAL_MILLIS: u128 = 500;
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ mod id;
|
|||
mod mode;
|
||||
mod position;
|
||||
mod settings;
|
||||
mod redraw_request;
|
||||
mod user_attention;
|
||||
|
||||
pub use action::Action;
|
||||
|
|
@ -15,4 +16,23 @@ pub use id::Id;
|
|||
pub use mode::Mode;
|
||||
pub use position::Position;
|
||||
pub use settings::Settings;
|
||||
pub use redraw_request::RedrawRequest;
|
||||
pub use user_attention::UserAttention;
|
||||
|
||||
use crate::subscription::{self, Subscription};
|
||||
use crate::time::Instant;
|
||||
|
||||
/// Subscribes to the frames of the window of the running application.
|
||||
///
|
||||
/// The resulting [`Subscription`] will produce items at a rate equal to the
|
||||
/// refresh rate of the window. Note that this rate may be variable, as it is
|
||||
/// normally managed by the graphics driver and/or the OS.
|
||||
///
|
||||
/// In any case, this [`Subscription`] is useful to smoothly draw application-driven
|
||||
/// animations without missing any frames.
|
||||
pub fn frames() -> Subscription<Instant> {
|
||||
subscription::raw_events(|event, _status| match event {
|
||||
crate::Event::Window(Event::RedrawRequested(at)) => Some(at),
|
||||
_ => None,
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
use crate::time::Instant;
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
/// A window-related event.
|
||||
|
|
@ -19,6 +21,11 @@ pub enum Event {
|
|||
height: u32,
|
||||
},
|
||||
|
||||
/// A window redraw was requested.
|
||||
///
|
||||
/// The [`Instant`] contains the current time.
|
||||
RedrawRequested(Instant),
|
||||
|
||||
/// The user has requested for the window to close.
|
||||
///
|
||||
/// Usually, you will want to terminate the execution whenever this event
|
||||
|
|
|
|||
38
native/src/window/redraw_request.rs
Normal file
38
native/src/window/redraw_request.rs
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
use crate::time::Instant;
|
||||
|
||||
/// A request to redraw a window.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub enum RedrawRequest {
|
||||
/// Redraw the next frame.
|
||||
NextFrame,
|
||||
|
||||
/// Redraw at the given time.
|
||||
At(Instant),
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
#[test]
|
||||
fn ordering() {
|
||||
let now = Instant::now();
|
||||
let later = now + Duration::from_millis(10);
|
||||
|
||||
assert_eq!(RedrawRequest::NextFrame, RedrawRequest::NextFrame);
|
||||
assert_eq!(RedrawRequest::At(now), RedrawRequest::At(now));
|
||||
|
||||
assert!(RedrawRequest::NextFrame < RedrawRequest::At(now));
|
||||
assert!(RedrawRequest::At(now) > RedrawRequest::NextFrame);
|
||||
assert!(RedrawRequest::At(now) < RedrawRequest::At(later));
|
||||
assert!(RedrawRequest::At(later) > RedrawRequest::At(now));
|
||||
|
||||
assert!(RedrawRequest::NextFrame <= RedrawRequest::NextFrame);
|
||||
assert!(RedrawRequest::NextFrame <= RedrawRequest::At(now));
|
||||
assert!(RedrawRequest::At(now) >= RedrawRequest::NextFrame);
|
||||
assert!(RedrawRequest::At(now) <= RedrawRequest::At(now));
|
||||
assert!(RedrawRequest::At(now) <= RedrawRequest::At(later));
|
||||
assert!(RedrawRequest::At(later) >= RedrawRequest::At(now));
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue