feat: rectangle tracker container

This commit is contained in:
Ashley Wulber 2022-12-14 16:51:22 -05:00 committed by Michael Murphy
parent db8b53b836
commit 8b1014a754
5 changed files with 363 additions and 341 deletions

View file

@ -5,12 +5,14 @@ use cosmic::{
iced::widget::{
column, container, horizontal_space, pick_list, progress_bar, radio, row, slider,
},
iced::{self, Alignment, Application, Command, Length, wayland::SurfaceIdWrapper},
iced::{self, wayland::SurfaceIdWrapper, Alignment, Application, Command, Length},
iced_lazy::responsive,
theme::{self, Theme},
widget::{button, nav_button, nav_bar, nav_bar_page, nav_bar_section, header_bar, settings, scrollable, toggler},
Element,
ElementExt,
widget::{
button, header_bar, nav_bar, nav_bar_page, nav_bar_section, nav_button, scrollable,
settings, toggler, rectangle_tracker::{RectangleTracker, rectangle_tracker_subscription, RectangleUpdate},
},
Element, ElementExt
};
use std::{collections::BTreeMap, vec};
use theme::Button as ButtonTheme;
@ -29,6 +31,7 @@ pub struct Window {
show_minimize: bool,
show_maximize: bool,
exit: bool,
rectangle_tracker: Option<RectangleTracker<u32>>,
}
impl Window {
@ -49,7 +52,7 @@ impl Window {
}
#[allow(dead_code)]
#[derive(Clone, Copy, Debug)]
#[derive(Clone, Debug)]
pub enum Message {
Page(u8),
Debug(bool),
@ -66,6 +69,7 @@ pub enum Message {
Minimize,
Maximize,
InputChanged,
Rectangle(RectangleUpdate<u32>)
}
impl Application for Window {
@ -99,7 +103,7 @@ impl Application for Window {
Message::SliderChanged(value) => self.slider_value = value,
Message::CheckboxToggled(value) => {
self.checkbox_value = value;
},
}
Message::TogglerToggled(value) => self.toggler_value = value,
Message::PickListSelected(value) => self.pick_list_selected = Some(value),
Message::Close => self.exit = true,
@ -108,8 +112,11 @@ impl Application for Window {
Message::Minimize => todo!(),
Message::Maximize => todo!(),
Message::RowSelected(row) => println!("Selected row {row}"),
Message::InputChanged => {},
Message::InputChanged => {}
Message::Rectangle(r) => match r {
RectangleUpdate::Rectangle(r) => {dbg!(r);},
RectangleUpdate::Init(t) => {self.rectangle_tracker.replace(t);},
},
}
Command::none()
@ -124,7 +131,7 @@ impl Application for Window {
nav_button("Settings")
.on_sidebar_toggled(Message::ToggleSidebar)
.sidebar_active(self.sidebar_toggled)
.into()
.into(),
);
if self.show_maximize {
@ -226,13 +233,21 @@ impl Application for Window {
))
},
);
let secondary = button(ButtonTheme::Secondary)
.text("Secondary")
.on_press(Message::ButtonPressed);
let secondary = if let Some(tracker) = self.rectangle_tracker.as_ref() {
tracker.container(0, secondary).into()
} else {
secondary.into()
};
let content: Element<_> = settings::view_column(vec![
settings::view_section("Debug")
.add(settings::item("Debug theme", choose_theme))
.add(settings::item(
"Debug layout",
toggler(String::from("Debug layout"), self.debug, Message::Debug)
toggler(String::from("Debug layout"), self.debug, Message::Debug),
))
.into(),
settings::view_section("Buttons")
@ -241,10 +256,7 @@ impl Application for Window {
.text("Primary")
.on_press(Message::ButtonPressed)
.into(),
button(ButtonTheme::Secondary)
.text("Secondary")
.on_press(Message::ButtonPressed)
.into(),
secondary,
button(ButtonTheme::Positive)
.text("Positive")
.on_press(Message::ButtonPressed)
@ -256,7 +268,7 @@ impl Application for Window {
button(ButtonTheme::Text)
.text("Text")
.on_press(Message::ButtonPressed)
.into()
.into(),
]))
.add(settings::item_row(vec![
button(ButtonTheme::Primary).text("Primary").into(),
@ -267,28 +279,31 @@ impl Application for Window {
]))
.into(),
settings::view_section("Controls")
.add(settings::item("Toggler", toggler(None, self.toggler_value, Message::TogglerToggled)))
.add(settings::item(
"Toggler",
toggler(None, self.toggler_value, Message::TogglerToggled),
))
.add(settings::item(
"Pick List (TODO)",
pick_list(
vec!["Option 1", "Option 2", "Option 3", "Option 4",],
vec!["Option 1", "Option 2", "Option 3", "Option 4"],
self.pick_list_selected,
Message::PickListSelected
Message::PickListSelected,
)
.padding([8, 0, 8, 16])
.padding([8, 0, 8, 16]),
))
.add(settings::item(
"Slider",
slider(0.0..=100.0, self.slider_value, Message::SliderChanged)
.width(Length::Units(250))
.width(Length::Units(250)),
))
.add(settings::item(
"Progress",
progress_bar(0.0..=100.0, self.slider_value)
.width(Length::Units(250))
.height(Length::Units(4))
.height(Length::Units(4)),
))
.into()
.into(),
])
.into();
@ -323,8 +338,11 @@ impl Application for Window {
fn theme(&self) -> Theme {
self.theme
}
fn close_requested(&self, id: SurfaceIdWrapper) -> Self::Message {
Message::Close
}
fn subscription(&self) -> iced::Subscription<Self::Message> {
rectangle_tracker_subscription(0).map(|(i, e)| Message::Rectangle(e))
}
}

View file

@ -19,9 +19,6 @@ pub use self::nav_button::{NavButton, nav_button};
pub mod navigation;
pub use navigation::*;
pub mod popup;
pub use popup::*;
mod toggler;
pub use toggler::toggler;
@ -34,4 +31,6 @@ pub mod separator;
pub use separator::{horizontal_rule, vertical_rule};
pub mod spin_button;
pub use spin_button::{SpinButton, spin_button};
pub use spin_button::{SpinButton, spin_button};
pub mod rectangle_tracker;

View file

@ -1,314 +0,0 @@
// Copyright 2022 System76 <info@system76.com>
// SPDX-License-Identifier: MPL-2.0
use iced::futures::channel::mpsc::{unbounded, UnboundedReceiver, UnboundedSender};
use iced::futures::SinkExt;
use iced::{
futures::StreamExt,
widget::{container, Container},
Rectangle,
};
use iced_native::alignment::{self, Alignment};
use iced_native::command::platform_specific::wayland::popup::{SctkPopupSettings, SctkPositioner};
use iced_native::event::{self, Event};
use iced_native::layout;
use iced_native::mouse;
use iced_native::overlay;
use iced_native::renderer;
use iced_native::widget::{Operation, Tree};
use iced_native::{
window, Background, Clipboard, Color, Element, Layout, Length, Padding, Point, Shell, Widget,
};
use std::u32;
pub use iced_style::container::{Appearance, StyleSheet};
pub struct SizeTrackingContainer<'a, Message, Renderer>
where
Renderer: iced_native::Renderer,
Renderer::Theme: StyleSheet,
{
container: Container<'a, Message, Renderer>,
tx: UnboundedSender<Rectangle<i32>>,
}
impl<'a, Message, Renderer> SizeTrackingContainer<'a, Message, Renderer>
where
Renderer: iced_native::Renderer,
Renderer::Theme: StyleSheet,
{
/// Creates an empty [`Container`].
pub fn new<T>(content: T, tx: UnboundedSender<Rectangle<i32>>) -> Self
where
T: Into<Element<'a, Message, Renderer>>,
{
SizeTrackingContainer {
container: container(content),
tx,
}
}
/// Sets the [`Padding`] of the [`Container`].
pub fn padding<P: Into<Padding>>(mut self, padding: P) -> Self {
self.container = self.container.padding(padding);
self
}
/// Sets the width of the [`Container`].
pub fn width(mut self, width: Length) -> Self {
self.container = self.container.width(width);
self
}
/// Sets the height of the [`Container`].
pub fn height(mut self, height: Length) -> Self {
self.container = self.container.height(height);
self
}
/// Sets the maximum width of the [`Container`].
pub fn max_width(mut self, max_width: u32) -> Self {
self.container = self.container.max_width(max_width);
self
}
/// Sets the maximum height of the [`Container`] in pixels.
pub fn max_height(mut self, max_height: u32) -> Self {
self.container = self.container.max_height(max_height);
self
}
/// Sets the content alignment for the horizontal axis of the [`Container`].
pub fn align_x(mut self, alignment: alignment::Horizontal) -> Self {
self.container = self.container.align_x(alignment);
self
}
/// Sets the content alignment for the vertical axis of the [`Container`].
pub fn align_y(mut self, alignment: alignment::Vertical) -> Self {
self.container = self.container.align_y(alignment);
self
}
/// Centers the contents in the horizontal axis of the [`Container`].
pub fn center_x(mut self) -> Self {
self.container = self.container.center_x();
self
}
/// Centers the contents in the vertical axis of the [`Container`].
pub fn center_y(mut self) -> Self {
self.container = self.container.center_y();
self
}
/// Sets the style of the [`Container`].
pub fn style(mut self, style: impl Into<<Renderer::Theme as StyleSheet>::Style>) -> Self {
self.container = self.container.style(style);
self
}
}
impl<'a, Message, Renderer> Widget<Message, Renderer>
for SizeTrackingContainer<'a, Message, Renderer>
where
Renderer: iced_native::Renderer,
Renderer::Theme: container::StyleSheet,
{
fn children(&self) -> Vec<Tree> {
self.container.children()
}
fn diff(&self, tree: &mut Tree) {
self.container.diff(tree)
}
fn width(&self) -> Length {
Widget::width(&self.container)
}
fn height(&self) -> Length {
Widget::height(&self.container)
}
fn layout(&self, renderer: &Renderer, limits: &layout::Limits) -> layout::Node {
self.container.layout(renderer, limits)
}
fn on_event(
&mut self,
tree: &mut Tree,
event: Event,
layout: Layout<'_>,
cursor_position: Point,
renderer: &Renderer,
clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
) -> event::Status {
self.container.on_event(
&mut tree.children[0],
event,
layout,
cursor_position,
renderer,
clipboard,
shell,
)
}
fn mouse_interaction(
&self,
tree: &Tree,
layout: Layout<'_>,
cursor_position: Point,
viewport: &Rectangle,
renderer: &Renderer,
) -> mouse::Interaction {
self.container.mouse_interaction(
&tree.children[0],
layout,
cursor_position,
viewport,
renderer,
)
}
fn draw(
&self,
tree: &Tree,
renderer: &mut Renderer,
theme: &Renderer::Theme,
inherited_style: &renderer::Style,
layout: Layout<'_>,
cursor_position: Point,
viewport: &Rectangle,
) {
let Rectangle {
x,
y,
width,
height,
} = layout.bounds();
let _ = self.tx.unbounded_send(Rectangle {
x: x as i32,
y: y as i32,
width: width as i32,
height: height as i32,
});
self.container.draw(
&tree.children[0],
renderer,
theme,
inherited_style,
layout,
cursor_position,
viewport,
);
}
fn overlay<'b>(
&'b self,
tree: &'b mut Tree,
layout: Layout<'_>,
renderer: &Renderer,
) -> Option<overlay::Element<'b, Message, Renderer>> {
self.container
.overlay(&mut tree.children[0], layout, renderer)
}
}
pub struct PopupParentSubscription {
id: window::Id,
settings: SctkPopupSettings,
}
impl PopupParentSubscription {
pub fn new(id: window::Id, settings: SctkPopupSettings) -> Self {
Self { id, settings }
}
pub fn get_popup_container<'a, T, Message, Renderer>(
&self,
content: T,
tx: UnboundedSender<Rectangle<i32>>,
) -> SizeTrackingContainer<'a, Message, Renderer>
where
T: Into<Element<'a, Message, Renderer>>,
Renderer: iced_native::Renderer,
Renderer::Theme: StyleSheet,
{
SizeTrackingContainer::new(content, tx.clone())
}
pub fn subscription(&self) -> iced::Subscription<(window::Id, PositionerUpdate)> {
popup_resize(self.id, self.settings.clone())
}
}
pub fn popup_resize(
id: window::Id,
settings: SctkPopupSettings,
) -> iced::Subscription<(window::Id, PositionerUpdate)> {
iced_native::subscription::unfold(
id,
State::Init(settings.positioner.anchor_rect.clone()),
move |state| rectangle_size(id, state),
)
.with(settings)
.map(|(settings, (id, update))| match update {
RectangleUpdate::Update(rect) => {
let mut new_pos = settings.positioner.clone();
new_pos.anchor_rect = rect;
(id, PositionerUpdate::Update(new_pos))
}
RectangleUpdate::Finished => (id, PositionerUpdate::Finished),
RectangleUpdate::Sender(sender) => (id, PositionerUpdate::Sender(sender)),
})
}
#[derive(Debug, Clone)]
pub enum PositionerUpdate {
Sender(UnboundedSender<Rectangle<i32>>),
Update(SctkPositioner),
Finished,
}
#[derive(Debug, Clone)]
pub enum RectangleUpdate {
Sender(UnboundedSender<Rectangle<i32>>),
Update(Rectangle<i32>),
Finished,
}
pub enum State {
Init(Rectangle<i32>),
WaitForUpdate(Rectangle<i32>, UnboundedReceiver<Rectangle<i32>>),
Finished,
}
async fn rectangle_size<I: Copy>(id: I, state: State) -> (Option<(I, RectangleUpdate)>, State) {
match state {
State::Init(rectangle) => {
let (tx, rx) = unbounded();
(
Some((id, RectangleUpdate::Sender(tx))),
State::WaitForUpdate(rectangle, rx),
)
}
State::WaitForUpdate(old_rectangle, mut rx) => {
let response = rx.next().await;
match response {
Some(new_rectangle) => {
let new_update = if new_rectangle == old_rectangle {
None
} else {
Some((id, RectangleUpdate::Update(new_rectangle)))
};
(new_update, State::WaitForUpdate(new_rectangle, rx))
}
None => (Some((id, RectangleUpdate::Finished)), State::Finished),
}
}
State::Finished => iced::futures::future::pending().await,
}
}

View file

@ -0,0 +1,244 @@
mod subscription;
use iced::futures::channel::mpsc::UnboundedSender;
use iced::widget::Container;
pub use subscription::*;
use iced_native::alignment;
use iced_native::event::{self, Event};
use iced_native::layout;
use iced_native::mouse;
use iced_native::overlay;
use iced_native::renderer;
use iced_native::widget::{Operation, Tree};
use iced_native::{Clipboard, Element, Layout, Length, Padding, Point, Rectangle, Shell, Widget};
use std::{fmt::Debug, hash::Hash};
pub use iced_style::container::{Appearance, StyleSheet};
#[derive(Clone, Debug)]
pub struct RectangleTracker<I> {
tx: UnboundedSender<(I, Rectangle)>,
}
impl<I> RectangleTracker<I>
where
I: Hash + Copy + Send + Sync + Debug,
{
pub fn container<'a, Message: 'static, T>(
&self,
id: I,
content: T,
) -> RectangleTrackingContainer<'a, Message, crate::Renderer, I>
where
I: 'a,
T: Into<Element<'a, Message, crate::Renderer>>,
{
RectangleTrackingContainer::new(content, id, self.tx.clone())
}
}
/// An element decorating some content.
///
/// It is normally used for alignment purposes.
#[allow(missing_debug_implementations)]
pub struct RectangleTrackingContainer<'a, Message, Renderer, I>
where
Renderer: iced_native::Renderer,
Renderer::Theme: StyleSheet,
{
tx: UnboundedSender<(I, Rectangle)>,
id: I,
container: Container<'a, Message, Renderer>,
}
impl<'a, Message, Renderer, I> RectangleTrackingContainer<'a, Message, Renderer, I>
where
Renderer: iced_native::Renderer,
Renderer::Theme: StyleSheet,
I: 'a + Hash + Copy + Send + Sync + Debug,
{
/// Creates an empty [`Container`].
pub(crate) fn new<T>(content: T, id: I, tx: UnboundedSender<(I, Rectangle)>) -> Self
where
T: Into<Element<'a, Message, Renderer>>,
{
RectangleTrackingContainer {
id,
tx,
container: Container::new(content),
}
}
/// Sets the [`Padding`] of the [`Container`].
pub fn padding<P: Into<Padding>>(mut self, padding: P) -> Self {
self.container = self.container.padding(padding);
self
}
/// Sets the width of the [`self.`].
pub fn width(mut self, width: Length) -> Self {
self.container = self.container.width(width);
self
}
/// Sets the height of the [`Container`].
pub fn height(mut self, height: Length) -> Self {
self.container = self.container.height(height);
self
}
/// Sets the maximum width of the [`Container`].
pub fn max_width(mut self, max_width: u32) -> Self {
self.container = self.container.max_width(max_width);
self
}
/// Sets the maximum height of the [`Container`] in pixels.
pub fn max_height(mut self, max_height: u32) -> Self {
self.container = self.container.max_height(max_height);
self
}
/// Sets the content alignment for the horizontal axis of the [`Container`].
pub fn align_x(mut self, alignment: alignment::Horizontal) -> Self {
self.container = self.container.align_x(alignment);
self
}
/// Sets the content alignment for the vertical axis of the [`Container`].
pub fn align_y(mut self, alignment: alignment::Vertical) -> Self {
self.container = self.container.align_y(alignment);
self
}
/// Centers the contents in the horizontal axis of the [`Container`].
pub fn center_x(mut self) -> Self {
self.container = self.container.center_x();
self
}
/// Centers the contents in the vertical axis of the [`Container`].
pub fn center_y(mut self) -> Self {
self.container = self.container.center_y();
self
}
/// Sets the style of the [`Container`].
pub fn style(mut self, style: impl Into<<Renderer::Theme as StyleSheet>::Style>) -> Self {
self.container = self.container.style(style);
self
}
}
impl<'a, Message, Renderer, I> Widget<Message, Renderer>
for RectangleTrackingContainer<'a, Message, Renderer, I>
where
Renderer: iced_native::Renderer,
Renderer::Theme: StyleSheet,
I: 'a + Hash + Copy + Send + Sync + Debug,
{
fn children(&self) -> Vec<Tree> {
self.container.children()
}
fn diff(&self, tree: &mut Tree) {
self.container.diff(tree);
}
fn width(&self) -> Length {
Widget::width(&self.container)
}
fn height(&self) -> Length {
Widget::height(&self.container)
}
fn layout(&self, renderer: &Renderer, limits: &layout::Limits) -> layout::Node {
self.container.layout(renderer, limits)
}
fn operate(&self, tree: &mut Tree, layout: Layout<'_>, operation: &mut dyn Operation<Message>) {
self.container.operate(tree, layout, operation)
}
fn on_event(
&mut self,
tree: &mut Tree,
event: Event,
layout: Layout<'_>,
cursor_position: Point,
renderer: &Renderer,
clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
) -> event::Status {
self.container.on_event(
tree,
event,
layout,
cursor_position,
renderer,
clipboard,
shell,
)
}
fn mouse_interaction(
&self,
tree: &Tree,
layout: Layout<'_>,
cursor_position: Point,
viewport: &Rectangle,
renderer: &Renderer,
) -> mouse::Interaction {
self.container
.mouse_interaction(tree, layout, cursor_position, viewport, renderer)
}
fn draw(
&self,
tree: &Tree,
renderer: &mut Renderer,
theme: &Renderer::Theme,
renderer_style: &renderer::Style,
layout: Layout<'_>,
cursor_position: Point,
viewport: &Rectangle,
) {
let _ = self.tx.unbounded_send((self.id, layout.bounds()));
self.container.draw(
tree,
renderer,
theme,
renderer_style,
layout,
cursor_position,
viewport,
)
}
fn overlay<'b>(
&'b self,
tree: &'b mut Tree,
layout: Layout<'_>,
renderer: &Renderer,
) -> Option<overlay::Element<'b, Message, Renderer>> {
self.container.overlay(tree, layout, renderer)
}
}
impl<'a, Message, Renderer, I> From<RectangleTrackingContainer<'a, Message, Renderer, I>>
for Element<'a, Message, Renderer>
where
Message: 'a,
Renderer: 'a + iced_native::Renderer,
Renderer::Theme: StyleSheet,
I: 'a + Hash + Copy + Send + Sync + Debug,
{
fn from(
column: RectangleTrackingContainer<'a, Message, Renderer, I>,
) -> Element<'a, Message, Renderer> {
Element::new(column)
}
}

View file

@ -0,0 +1,75 @@
use iced::{
futures::{
channel::mpsc::{unbounded, UnboundedReceiver},
StreamExt,
},
subscription, Rectangle,
};
use std::{fmt::Debug, hash::Hash, collections::HashMap};
use super::RectangleTracker;
pub fn rectangle_tracker_subscription<
I: 'static + Hash + Copy + Send + Sync + Debug,
R: 'static + Hash + Copy + Send + Sync + Debug + Eq,
>(
id: I,
) -> iced::Subscription<(I, RectangleUpdate<R>)> {
subscription::unfold(id, State::Ready, move |state| start_listening(id, state))
}
pub enum State<I> {
Ready,
Waiting(UnboundedReceiver<(I, Rectangle)>, HashMap<I, Rectangle>),
Finished,
}
async fn start_listening<I: Copy, R: 'static + Hash + Copy + Send + Sync + Debug + Eq>(
id: I,
state: State<R>,
) -> (Option<(I, RectangleUpdate<R>)>, State<R>) {
match state {
State::Ready => {
let (tx, rx) = unbounded();
return (
Some((id, RectangleUpdate::Init(RectangleTracker { tx }))),
State::Waiting(rx, HashMap::new()),
);
}
State::Waiting(mut rx, mut map) => match rx.next().await {
Some(u) =>
{
if let Some(prev) = map.get(&u.0) {
let new = u.1;
if prev.width != new.width || prev.height != new.height || prev.x != new.x || prev.y != new.y {
map.insert(u.0, new);
return (
Some((id, RectangleUpdate::Rectangle(u))),
State::Waiting(rx, map),
);
}
} else {
map.insert(u.0, u.1);
return (
Some((id, RectangleUpdate::Rectangle(u))),
State::Waiting(rx, map),
);
}
return (None, State::Waiting(rx, map))
},
None => (None, State::Finished),
},
State::Finished => iced::futures::future::pending().await,
}
}
#[derive(Clone, Debug)]
pub enum RectangleUpdate<I>
where
I: 'static + Hash + Copy + Send + Sync + Debug,
{
Rectangle((I, Rectangle)),
Init(RectangleTracker<I>),
}