Widget operations for multi-window.

This commit is contained in:
Bingus 2023-02-15 14:31:16 -08:00
parent 367fea5dc8
commit 64e0e817c2
No known key found for this signature in database
GPG key ID: 5F84D2AA40A9F170
4 changed files with 169 additions and 113 deletions

View file

@ -12,6 +12,7 @@ use iced::{Color, Command, Element, Length, Settings, Size, Subscription};
use iced_lazy::responsive; use iced_lazy::responsive;
use iced_native::{event, subscription, Event}; use iced_native::{event, subscription, Event};
use iced_native::widget::scrollable::{Properties, RelativeOffset};
use iced_native::window::Id; use iced_native::window::Id;
use std::collections::HashMap; use std::collections::HashMap;
@ -56,6 +57,7 @@ enum WindowMessage {
CloseFocused, CloseFocused,
SelectedWindow(pane_grid::Pane, SelectableWindow), SelectedWindow(pane_grid::Pane, SelectableWindow),
CloseWindow, CloseWindow,
SnapToggle,
} }
impl Application for Example { impl Application for Example {
@ -94,6 +96,25 @@ impl Application for Example {
fn update(&mut self, message: Message) -> Command<Message> { fn update(&mut self, message: Message) -> Command<Message> {
let Message::Window(id, message) = message; let Message::Window(id, message) = message;
match message { match message {
WindowMessage::SnapToggle => {
let window = self.windows.get_mut(&id).unwrap();
if let Some(focused) = &window.focus {
let pane = window.panes.get_mut(focused).unwrap();
let cmd = scrollable::snap_to(
pane.scrollable_id.clone(),
if pane.snapped {
RelativeOffset::START
} else {
RelativeOffset::END
},
);
pane.snapped = !pane.snapped;
return cmd;
}
}
WindowMessage::Split(axis, pane) => { WindowMessage::Split(axis, pane) => {
let window = self.windows.get_mut(&id).unwrap(); let window = self.windows.get_mut(&id).unwrap();
let result = window.panes.split( let result = window.panes.split(
@ -311,7 +332,13 @@ impl Application for Example {
}); });
pane_grid::Content::new(responsive(move |size| { pane_grid::Content::new(responsive(move |size| {
view_content(id, total_panes, pane.is_pinned, size) view_content(
id,
pane.scrollable_id.clone(),
total_panes,
pane.is_pinned,
size,
)
})) }))
.title_bar(title_bar) .title_bar(title_bar)
.style(if is_focused { .style(if is_focused {
@ -403,24 +430,29 @@ impl std::fmt::Display for SelectableWindow {
#[derive(Debug)] #[derive(Debug)]
struct Pane { struct Pane {
id: usize, id: usize,
pub scrollable_id: scrollable::Id,
pub axis: pane_grid::Axis, pub axis: pane_grid::Axis,
pub is_pinned: bool, pub is_pinned: bool,
pub is_moving: bool, pub is_moving: bool,
pub snapped: bool,
} }
impl Pane { impl Pane {
fn new(id: usize, axis: pane_grid::Axis) -> Self { fn new(id: usize, axis: pane_grid::Axis) -> Self {
Self { Self {
id, id,
scrollable_id: scrollable::Id::new(format!("{:?}", id)),
axis, axis,
is_pinned: false, is_pinned: false,
is_moving: false, is_moving: false,
snapped: false,
} }
} }
} }
fn view_content<'a>( fn view_content<'a>(
pane: pane_grid::Pane, pane: pane_grid::Pane,
scrollable_id: scrollable::Id,
total_panes: usize, total_panes: usize,
is_pinned: bool, is_pinned: bool,
size: Size, size: Size,
@ -445,7 +477,8 @@ fn view_content<'a>(
button( button(
"Split vertically", "Split vertically",
WindowMessage::Split(pane_grid::Axis::Vertical, pane), WindowMessage::Split(pane_grid::Axis::Vertical, pane),
) ),
button("Snap", WindowMessage::SnapToggle,)
] ]
.spacing(5) .spacing(5)
.max_width(150); .max_width(150);
@ -462,15 +495,22 @@ fn view_content<'a>(
controls, controls,
] ]
.width(Length::Fill) .width(Length::Fill)
.height(Length::Units(800))
.spacing(10) .spacing(10)
.align_items(Alignment::Center); .align_items(Alignment::Center);
container(scrollable(content)) Element::from(
container(
scrollable(content)
.vertical_scroll(Properties::new())
.id(scrollable_id),
)
.width(Length::Fill) .width(Length::Fill)
.height(Length::Fill) .height(Length::Fill)
.padding(5) .padding(5)
.center_y() .center_y(),
.into() )
.explain(Color::default())
} }
fn view_controls<'a>( fn view_controls<'a>(

View file

@ -34,6 +34,7 @@ use std::num::NonZeroU32;
#[cfg(feature = "tracing")] #[cfg(feature = "tracing")]
use tracing::{info_span, instrument::Instrument}; use tracing::{info_span, instrument::Instrument};
use iced_native::widget::operation;
#[allow(unsafe_code)] #[allow(unsafe_code)]
const ONE: NonZeroU32 = unsafe { NonZeroU32::new_unchecked(1) }; const ONE: NonZeroU32 = unsafe { NonZeroU32::new_unchecked(1) };
@ -380,7 +381,7 @@ async fn run_instance<A, E, C>(
let mut clipboard = let mut clipboard =
Clipboard::connect(windows.values().next().expect("No window found")); Clipboard::connect(windows.values().next().expect("No window found"));
let mut cache = user_interface::Cache::default(); let mut caches = HashMap::new();
let mut current_context_window = None; let mut current_context_window = None;
let mut window_ids: HashMap<_, _> = windows let mut window_ids: HashMap<_, _> = windows
.iter() .iter()
@ -422,13 +423,10 @@ async fn run_instance<A, E, C>(
let _ = interfaces.insert(id, user_interface); let _ = interfaces.insert(id, user_interface);
} }
{
let state = states.values().next().expect("No state found.");
run_command( run_command(
&application, &application,
&mut cache, &mut caches,
state, &states,
&mut renderer, &mut renderer,
init_command, init_command,
&mut runtime, &mut runtime,
@ -438,7 +436,7 @@ async fn run_instance<A, E, C>(
&windows, &windows,
|| compositor.fetch_information(), || compositor.fetch_information(),
); );
}
runtime.track(application.subscription().map(Event::Application)); runtime.track(application.subscription().map(Event::Application));
let mut mouse_interaction = mouse::Interaction::default(); let mut mouse_interaction = mouse::Interaction::default();
@ -501,8 +499,7 @@ async fn run_instance<A, E, C>(
user_interface::State::Outdated user_interface::State::Outdated
) )
{ {
let state = &mut states.get_mut(&id).unwrap(); let user_interfaces: HashMap<_, _> =
let pure_states: HashMap<_, _> =
ManuallyDrop::into_inner(interfaces) ManuallyDrop::into_inner(interfaces)
.drain() .drain()
.map(|(id, interface)| { .map(|(id, interface)| {
@ -513,8 +510,8 @@ async fn run_instance<A, E, C>(
// Update application // Update application
update( update(
&mut application, &mut application,
&mut cache, &mut caches,
state, &states,
&mut renderer, &mut renderer,
&mut runtime, &mut runtime,
&mut clipboard, &mut clipboard,
@ -526,7 +523,7 @@ async fn run_instance<A, E, C>(
); );
// Update window // Update window
state.synchronize( states.get_mut(&id).unwrap().synchronize(
&application, &application,
id, id,
windows.get(&id).expect("No window found with ID."), windows.get(&id).expect("No window found with ID."),
@ -539,7 +536,7 @@ async fn run_instance<A, E, C>(
&mut renderer, &mut renderer,
&mut debug, &mut debug,
&states, &states,
pure_states, user_interfaces,
)); ));
if should_exit { if should_exit {
@ -590,7 +587,8 @@ async fn run_instance<A, E, C>(
event::Event::UserEvent(event) => match event { event::Event::UserEvent(event) => match event {
Event::Application(message) => messages.push(message), Event::Application(message) => messages.push(message),
Event::WindowCreated(id, window) => { Event::WindowCreated(id, window) => {
let state = multi_window::State::new(&application, id, &window); let state =
multi_window::State::new(&application, id, &window);
let user_interface = multi_window::build_user_interface( let user_interface = multi_window::build_user_interface(
&application, &application,
user_interface::Cache::default(), user_interface::Cache::default(),
@ -768,7 +766,10 @@ async fn run_instance<A, E, C>(
)); ));
} }
} else { } else {
log::error!("Window state not found for id: {:?}", window_id); log::error!(
"Window state not found for id: {:?}",
window_id
);
} }
} else { } else {
log::error!("Window not found for id: {:?}", window_id); log::error!("Window not found for id: {:?}", window_id);
@ -786,8 +787,8 @@ async fn run_instance<A, E, C>(
/// resulting [`Command`], and tracking its [`Subscription`]. /// resulting [`Command`], and tracking its [`Subscription`].
pub fn update<A: Application, E: Executor>( pub fn update<A: Application, E: Executor>(
application: &mut A, application: &mut A,
cache: &mut user_interface::Cache, caches: &mut HashMap<window::Id, user_interface::Cache>,
state: &multi_window::State<A>, states: &HashMap<window::Id, multi_window::State<A>>,
renderer: &mut A::Renderer, renderer: &mut A::Renderer,
runtime: &mut Runtime<E, Proxy<Event<A::Message>>, Event<A::Message>>, runtime: &mut Runtime<E, Proxy<Event<A::Message>>, Event<A::Message>>,
clipboard: &mut Clipboard, clipboard: &mut Clipboard,
@ -797,6 +798,7 @@ pub fn update<A: Application, E: Executor>(
windows: &HashMap<window::Id, winit::window::Window>, windows: &HashMap<window::Id, winit::window::Window>,
graphics_info: impl FnOnce() -> iced_graphics::compositor::Information + Copy, graphics_info: impl FnOnce() -> iced_graphics::compositor::Information + Copy,
) where ) where
A: Application + 'static,
<A::Renderer as crate::Renderer>::Theme: StyleSheet, <A::Renderer as crate::Renderer>::Theme: StyleSheet,
{ {
for message in messages.drain(..) { for message in messages.drain(..) {
@ -808,8 +810,8 @@ pub fn update<A: Application, E: Executor>(
run_command( run_command(
application, application,
cache, caches,
state, &states,
renderer, renderer,
command, command,
runtime, runtime,
@ -828,8 +830,8 @@ pub fn update<A: Application, E: Executor>(
/// Runs the actions of a [`Command`]. /// Runs the actions of a [`Command`].
pub fn run_command<A, E>( pub fn run_command<A, E>(
application: &A, application: &A,
cache: &mut user_interface::Cache, caches: &mut HashMap<window::Id, user_interface::Cache>,
state: &multi_window::State<A>, states: &HashMap<window::Id, multi_window::State<A>>,
renderer: &mut A::Renderer, renderer: &mut A::Renderer,
command: Command<A::Message>, command: Command<A::Message>,
runtime: &mut Runtime<E, Proxy<Event<A::Message>>, Event<A::Message>>, runtime: &mut Runtime<E, Proxy<Event<A::Message>>, Event<A::Message>>,
@ -839,7 +841,7 @@ pub fn run_command<A, E>(
windows: &HashMap<window::Id, winit::window::Window>, windows: &HashMap<window::Id, winit::window::Window>,
_graphics_info: impl FnOnce() -> iced_graphics::compositor::Information + Copy, _graphics_info: impl FnOnce() -> iced_graphics::compositor::Information + Copy,
) where ) where
A: Application, A: Application + 'static,
E: Executor, E: Executor,
<A::Renderer as crate::Renderer>::Theme: StyleSheet, <A::Renderer as crate::Renderer>::Theme: StyleSheet,
{ {
@ -967,21 +969,19 @@ pub fn run_command<A, E>(
} }
}, },
command::Action::Widget(action) => { command::Action::Widget(action) => {
use crate::widget::operation; let mut current_caches = std::mem::take(caches);
let mut current_cache = std::mem::take(cache);
let mut current_operation = Some(action.into_operation()); let mut current_operation = Some(action.into_operation());
let mut user_interface = multi_window::build_user_interface( let mut user_interfaces = multi_window::build_user_interfaces(
application, application,
current_cache,
renderer, renderer,
state.logical_size(),
debug, debug,
window::Id::MAIN, // TODO(derezzedex): run the operation on every widget tree states,
current_caches,
); );
while let Some(mut operation) = current_operation.take() { while let Some(mut operation) = current_operation.take() {
for user_interface in user_interfaces.values_mut() {
user_interface.operate(renderer, operation.as_mut()); user_interface.operate(renderer, operation.as_mut());
match operation.finish() { match operation.finish() {
@ -996,9 +996,15 @@ pub fn run_command<A, E>(
} }
} }
} }
}
current_cache = user_interface.into_cache(); let user_interfaces: HashMap<_, _> = user_interfaces
*cache = current_cache; .drain()
.map(|(id, interface)| (id, interface.into_cache()))
.collect();
current_caches = user_interfaces;
*caches = current_caches;
} }
} }
} }
@ -1010,7 +1016,7 @@ pub fn build_user_interfaces<'a, A>(
renderer: &mut A::Renderer, renderer: &mut A::Renderer,
debug: &mut Debug, debug: &mut Debug,
states: &HashMap<window::Id, multi_window::State<A>>, states: &HashMap<window::Id, multi_window::State<A>>,
mut pure_states: HashMap<window::Id, user_interface::Cache>, mut user_interfaces: HashMap<window::Id, user_interface::Cache>,
) -> HashMap< ) -> HashMap<
window::Id, window::Id,
iced_winit::UserInterface< iced_winit::UserInterface<
@ -1025,7 +1031,7 @@ where
{ {
let mut interfaces = HashMap::new(); let mut interfaces = HashMap::new();
for (id, pure_state) in pure_states.drain() { for (id, pure_state) in user_interfaces.drain() {
let state = &states.get(&id).unwrap(); let state = &states.get(&id).unwrap();
let user_interface = multi_window::build_user_interface( let user_interface = multi_window::build_user_interface(

View file

@ -21,6 +21,7 @@ pub use user_attention::UserAttention;
use crate::subscription::{self, Subscription}; use crate::subscription::{self, Subscription};
use crate::time::Instant; use crate::time::Instant;
use crate::window;
/// Subscribes to the frames of the window of the running application. /// Subscribes to the frames of the window of the running application.
/// ///
@ -42,6 +43,8 @@ pub fn frames() -> Subscription<Frame> {
/// The returned `Frame` for a framerate subscription. /// The returned `Frame` for a framerate subscription.
#[derive(Debug)] #[derive(Debug)]
pub struct Frame { pub struct Frame {
/// The `window::Id` that the `Frame` was produced in.
pub id: Id, pub id: Id,
/// The `Instant` at which the frame was produced.
pub at: Instant, pub at: Instant,
} }

View file

@ -4,12 +4,12 @@ mod state;
pub use state::State; pub use state::State;
use crate::clipboard::{self, Clipboard}; use crate::clipboard::{self, Clipboard};
use crate::conversion;
use crate::mouse; use crate::mouse;
use crate::renderer; use crate::renderer;
use crate::settings; use crate::settings;
use crate::widget::operation; use crate::widget::operation;
use crate::window; use crate::window;
use crate::{conversion, multi_window};
use crate::{ use crate::{
Command, Debug, Element, Error, Executor, Proxy, Renderer, Runtime, Command, Debug, Element, Error, Executor, Proxy, Renderer, Runtime,
Settings, Size, Subscription, Settings, Size, Subscription,
@ -335,7 +335,7 @@ async fn run_instance<A, E, C>(
let mut clipboard = let mut clipboard =
Clipboard::connect(windows.values().next().expect("No window found")); Clipboard::connect(windows.values().next().expect("No window found"));
let mut cache = user_interface::Cache::default(); let mut caches = HashMap::new();
let mut window_ids: HashMap<_, _> = windows let mut window_ids: HashMap<_, _> = windows
.iter() .iter()
.map(|(&id, window)| (window.id(), id)) .map(|(&id, window)| (window.id(), id))
@ -368,16 +368,13 @@ async fn run_instance<A, E, C>(
let _ = states.insert(id, state); let _ = states.insert(id, state);
let _ = surfaces.insert(id, surface); let _ = surfaces.insert(id, surface);
let _ = interfaces.insert(id, user_interface); let _ = interfaces.insert(id, user_interface);
let _ = caches.insert(id, user_interface::Cache::default());
} }
{
// TODO(derezzedex)
let state = states.values().next().expect("No state found");
run_command( run_command(
&application, &application,
&mut cache, &mut caches,
state, &states,
&mut renderer, &mut renderer,
init_command, init_command,
&mut runtime, &mut runtime,
@ -387,7 +384,7 @@ async fn run_instance<A, E, C>(
&windows, &windows,
|| compositor.fetch_information(), || compositor.fetch_information(),
); );
}
runtime.track(application.subscription().map(Event::Application)); runtime.track(application.subscription().map(Event::Application));
let mut mouse_interaction = mouse::Interaction::default(); let mut mouse_interaction = mouse::Interaction::default();
@ -455,8 +452,7 @@ async fn run_instance<A, E, C>(
user_interface::State::Outdated, user_interface::State::Outdated,
) )
{ {
let state = states.get_mut(&id).unwrap(); let user_interfaces: HashMap<_, _> =
let pure_states: HashMap<_, _> =
ManuallyDrop::into_inner(interfaces) ManuallyDrop::into_inner(interfaces)
.drain() .drain()
.map( .map(
@ -472,8 +468,8 @@ async fn run_instance<A, E, C>(
// Update application // Update application
update( update(
&mut application, &mut application,
&mut cache, &mut caches,
state, &states,
&mut renderer, &mut renderer,
&mut runtime, &mut runtime,
&mut clipboard, &mut clipboard,
@ -485,7 +481,7 @@ async fn run_instance<A, E, C>(
); );
// Update window // Update window
state.synchronize( states.get_mut(&id).unwrap().synchronize(
&application, &application,
id, id,
windows.get(&id).expect("No window found with ID."), windows.get(&id).expect("No window found with ID."),
@ -498,7 +494,7 @@ async fn run_instance<A, E, C>(
&mut renderer, &mut renderer,
&mut debug, &mut debug,
&states, &states,
pure_states, user_interfaces,
)); ));
if should_exit { if should_exit {
@ -579,6 +575,7 @@ async fn run_instance<A, E, C>(
let _ = interfaces.insert(id, user_interface); let _ = interfaces.insert(id, user_interface);
let _ = window_ids.insert(window.id(), id); let _ = window_ids.insert(window.id(), id);
let _ = windows.insert(id, window); let _ = windows.insert(id, window);
let _ = caches.insert(id, user_interface::Cache::default());
} }
Event::CloseWindow(id) => { Event::CloseWindow(id) => {
if let Some(window) = windows.get(&id) { if let Some(window) = windows.get(&id) {
@ -764,7 +761,10 @@ async fn run_instance<A, E, C>(
)); ));
} }
} else { } else {
log::error!("No window state found for id: {:?}", window_id); log::error!(
"No window state found for id: {:?}",
window_id
);
} }
} else { } else {
log::error!("No window found with id: {:?}", window_id); log::error!("No window found with id: {:?}", window_id);
@ -840,8 +840,8 @@ where
/// resulting [`Command`], and tracking its [`Subscription`]. /// resulting [`Command`], and tracking its [`Subscription`].
pub fn update<A: Application, E: Executor>( pub fn update<A: Application, E: Executor>(
application: &mut A, application: &mut A,
cache: &mut user_interface::Cache, caches: &mut HashMap<window::Id, user_interface::Cache>,
state: &State<A>, states: &HashMap<window::Id, State<A>>,
renderer: &mut A::Renderer, renderer: &mut A::Renderer,
runtime: &mut Runtime<E, Proxy<Event<A::Message>>, Event<A::Message>>, runtime: &mut Runtime<E, Proxy<Event<A::Message>>, Event<A::Message>>,
clipboard: &mut Clipboard, clipboard: &mut Clipboard,
@ -851,6 +851,7 @@ pub fn update<A: Application, E: Executor>(
windows: &HashMap<window::Id, winit::window::Window>, windows: &HashMap<window::Id, winit::window::Window>,
graphics_info: impl FnOnce() -> compositor::Information + Copy, graphics_info: impl FnOnce() -> compositor::Information + Copy,
) where ) where
A: Application + 'static,
<A::Renderer as crate::Renderer>::Theme: StyleSheet, <A::Renderer as crate::Renderer>::Theme: StyleSheet,
{ {
for message in messages.drain(..) { for message in messages.drain(..) {
@ -867,8 +868,8 @@ pub fn update<A: Application, E: Executor>(
run_command( run_command(
application, application,
cache, caches,
state, states,
renderer, renderer,
command, command,
runtime, runtime,
@ -887,8 +888,8 @@ pub fn update<A: Application, E: Executor>(
/// Runs the actions of a [`Command`]. /// Runs the actions of a [`Command`].
pub fn run_command<A, E>( pub fn run_command<A, E>(
application: &A, application: &A,
cache: &mut user_interface::Cache, caches: &mut HashMap<window::Id, user_interface::Cache>,
state: &State<A>, states: &HashMap<window::Id, State<A>>,
renderer: &mut A::Renderer, renderer: &mut A::Renderer,
command: Command<A::Message>, command: Command<A::Message>,
runtime: &mut Runtime<E, Proxy<Event<A::Message>>, Event<A::Message>>, runtime: &mut Runtime<E, Proxy<Event<A::Message>>, Event<A::Message>>,
@ -898,7 +899,7 @@ pub fn run_command<A, E>(
windows: &HashMap<window::Id, winit::window::Window>, windows: &HashMap<window::Id, winit::window::Window>,
_graphics_info: impl FnOnce() -> compositor::Information + Copy, _graphics_info: impl FnOnce() -> compositor::Information + Copy,
) where ) where
A: Application, A: Application + 'static,
E: Executor, E: Executor,
<A::Renderer as crate::Renderer>::Theme: StyleSheet, <A::Renderer as crate::Renderer>::Theme: StyleSheet,
{ {
@ -1024,19 +1025,19 @@ pub fn run_command<A, E>(
} }
}, },
command::Action::Widget(action) => { command::Action::Widget(action) => {
let mut current_cache = std::mem::take(cache); let mut current_caches = std::mem::take(caches);
let mut current_operation = Some(action.into_operation()); let mut current_operation = Some(action.into_operation());
let mut user_interface = build_user_interface( let mut user_interfaces = build_user_interfaces(
application, application,
current_cache,
renderer, renderer,
state.logical_size(),
debug, debug,
window::Id::MAIN, // TODO(derezzedex): run the operation on every widget tree states,
current_caches,
); );
while let Some(mut operation) = current_operation.take() { while let Some(mut operation) = current_operation.take() {
for user_interface in user_interfaces.values_mut() {
user_interface.operate(renderer, operation.as_mut()); user_interface.operate(renderer, operation.as_mut());
match operation.finish() { match operation.finish() {
@ -1051,9 +1052,15 @@ pub fn run_command<A, E>(
} }
} }
} }
}
current_cache = user_interface.into_cache(); let user_interfaces: HashMap<_, _> = user_interfaces
*cache = current_cache; .drain()
.map(|(id, interface)| (id, interface.into_cache()))
.collect();
current_caches = user_interfaces;
*caches = current_caches;
} }
} }
} }
@ -1065,7 +1072,7 @@ pub fn build_user_interfaces<'a, A>(
renderer: &mut A::Renderer, renderer: &mut A::Renderer,
debug: &mut Debug, debug: &mut Debug,
states: &HashMap<window::Id, State<A>>, states: &HashMap<window::Id, State<A>>,
mut pure_states: HashMap<window::Id, user_interface::Cache>, mut cached_user_interfaces: HashMap<window::Id, user_interface::Cache>,
) -> HashMap< ) -> HashMap<
window::Id, window::Id,
UserInterface< UserInterface<
@ -1080,12 +1087,12 @@ where
{ {
let mut interfaces = HashMap::new(); let mut interfaces = HashMap::new();
for (id, pure_state) in pure_states.drain() { for (id, cache) in cached_user_interfaces.drain() {
let state = &states.get(&id).unwrap(); let state = &states.get(&id).unwrap();
let user_interface = build_user_interface( let user_interface = build_user_interface(
application, application,
pure_state, cache,
renderer, renderer,
state.logical_size(), state.logical_size(),
debug, debug,