wip: popup helpers

This commit is contained in:
Ashley Wulber 2022-11-15 23:11:48 +01:00
parent c48fca40fe
commit 33779b8652
No known key found for this signature in database
GPG key ID: 5216D4F46A90A820
7 changed files with 321 additions and 3 deletions

View file

@ -11,6 +11,7 @@ default = ["wayland"]
debug = ["iced/debug"] debug = ["iced/debug"]
wayland = ["iced/wayland"] wayland = ["iced/wayland"]
wgpu = ["iced/wgpu"] wgpu = ["iced/wgpu"]
winit = ["iced/winit", "iced_winit"]
applet = ["cosmic-panel-config", "sctk"] applet = ["cosmic-panel-config", "sctk"]
[dependencies] [dependencies]

View file

@ -356,4 +356,8 @@ impl Application for Window {
fn theme(&self) -> Theme { fn theme(&self) -> Theme {
self.theme self.theme
} }
fn subscription(&self) -> iced_sctk::Subscription<Self::Message> {
Subscription::none()
}
} }

View file

@ -6,4 +6,4 @@ edition = "2021"
publish = false publish = false
[dependencies] [dependencies]
libcosmic = { path = "../..", default-features = false, features = ["debug", "wgpu"] } libcosmic = { path = "../..", default-features = false, features = ["debug", "wgpu", "winit"] }

View file

@ -124,4 +124,3 @@ where
.align_x(halign) .align_x(halign)
.align_y(valign) .align_y(valign)
} }

View file

@ -2,7 +2,7 @@ pub use iced;
pub use iced_lazy; pub use iced_lazy;
pub use iced_native; pub use iced_native;
pub use iced_style; pub use iced_style;
#[cfg(feature = "iced_winit")] #[cfg(feature = "winit")]
pub use iced_winit; pub use iced_winit;
#[cfg(feature = "applet")] #[cfg(feature = "applet")]

View file

@ -24,3 +24,6 @@ pub use list::*;
pub mod separator; pub mod separator;
pub use separator::*; pub use separator::*;
pub mod popup;
pub use popup::*;

311
src/widget/popup.rs Normal file
View file

@ -0,0 +1,311 @@
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,
}
}