Merge remote-tracking branch 'origin/master' into feat/multi-window-support
# Conflicts: # native/src/command/action.rs # native/src/window/action.rs # winit/src/window.rs
This commit is contained in:
commit
63fb608d8b
55 changed files with 1218 additions and 224 deletions
|
|
@ -15,10 +15,10 @@ A cross-platform GUI library for Rust focused on simplicity and type-safety.
|
||||||
Inspired by [Elm].
|
Inspired by [Elm].
|
||||||
|
|
||||||
<a href="https://gfycat.com/littlesanehalicore">
|
<a href="https://gfycat.com/littlesanehalicore">
|
||||||
<img src="https://thumbs.gfycat.com/LittleSaneHalicore-small.gif" height="350px">
|
<img src="https://thumbs.gfycat.com/LittleSaneHalicore-small.gif" width="275px">
|
||||||
</a>
|
</a>
|
||||||
<a href="https://gfycat.com/politeadorableiberianmole">
|
<a href="https://gfycat.com/politeadorableiberianmole">
|
||||||
<img src="https://thumbs.gfycat.com/PoliteAdorableIberianmole-small.gif" height="350px">
|
<img src="https://thumbs.gfycat.com/PoliteAdorableIberianmole-small.gif" width="273px">
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,11 @@ impl<T> Vector<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Vector {
|
||||||
|
/// The zero [`Vector`].
|
||||||
|
pub const ZERO: Self = Self::new(0.0, 0.0);
|
||||||
|
}
|
||||||
|
|
||||||
impl<T> std::ops::Add for Vector<T>
|
impl<T> std::ops::Add for Vector<T>
|
||||||
where
|
where
|
||||||
T: std::ops::Add<Output = T>,
|
T: std::ops::Add<Output = T>,
|
||||||
|
|
|
||||||
|
|
@ -177,7 +177,7 @@ impl Download {
|
||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
State::Downloading { .. } => {
|
State::Downloading { .. } => {
|
||||||
text(format!("Downloading... {:.2}%", current_progress)).into()
|
text(format!("Downloading... {current_progress:.2}%")).into()
|
||||||
}
|
}
|
||||||
State::Errored => column![
|
State::Errored => column![
|
||||||
"Something went wrong :(",
|
"Something went wrong :(",
|
||||||
|
|
|
||||||
|
|
@ -77,7 +77,7 @@ impl Application for Events {
|
||||||
let events = Column::with_children(
|
let events = Column::with_children(
|
||||||
self.last
|
self.last
|
||||||
.iter()
|
.iter()
|
||||||
.map(|event| text(format!("{:?}", event)).size(40))
|
.map(|event| text(format!("{event:?}")).size(40))
|
||||||
.map(Element::from)
|
.map(Element::from)
|
||||||
.collect(),
|
.collect(),
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -176,7 +176,7 @@ fn view_controls<'a>(
|
||||||
|
|
||||||
let speed_controls = row![
|
let speed_controls = row![
|
||||||
slider(1.0..=1000.0, speed as f32, Message::SpeedChanged),
|
slider(1.0..=1000.0, speed as f32, Message::SpeedChanged),
|
||||||
text(format!("x{}", speed)).size(16),
|
text(format!("x{speed}")).size(16),
|
||||||
]
|
]
|
||||||
.width(Length::Fill)
|
.width(Length::Fill)
|
||||||
.align_items(Alignment::Center)
|
.align_items(Alignment::Center)
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
|
||||||
pub enum Preset {
|
pub enum Preset {
|
||||||
Custom,
|
Custom,
|
||||||
|
#[default]
|
||||||
Xkcd,
|
Xkcd,
|
||||||
Glider,
|
Glider,
|
||||||
SmallExploder,
|
SmallExploder,
|
||||||
|
|
@ -114,12 +115,6 @@ impl Preset {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Preset {
|
|
||||||
fn default() -> Preset {
|
|
||||||
Preset::Xkcd
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::fmt::Display for Preset {
|
impl std::fmt::Display for Preset {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
write!(
|
write!(
|
||||||
|
|
|
||||||
|
|
@ -90,7 +90,7 @@ impl Program for Controls {
|
||||||
)
|
)
|
||||||
.push(sliders)
|
.push(sliders)
|
||||||
.push(
|
.push(
|
||||||
Text::new(format!("{:?}", background_color))
|
Text::new(format!("{background_color:?}"))
|
||||||
.size(14)
|
.size(14)
|
||||||
.style(Color::WHITE),
|
.style(Color::WHITE),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -49,7 +49,7 @@ impl Scene {
|
||||||
.expect("Cannot create shader");
|
.expect("Cannot create shader");
|
||||||
gl.shader_source(
|
gl.shader_source(
|
||||||
shader,
|
shader,
|
||||||
&format!("{}\n{}", shader_version, shader_source),
|
&format!("{shader_version}\n{shader_source}"),
|
||||||
);
|
);
|
||||||
gl.compile_shader(shader);
|
gl.compile_shader(shader);
|
||||||
if !gl.get_shader_compile_status(shader) {
|
if !gl.get_shader_compile_status(shader) {
|
||||||
|
|
|
||||||
|
|
@ -96,7 +96,7 @@ impl Program for Controls {
|
||||||
)
|
)
|
||||||
.push(sliders)
|
.push(sliders)
|
||||||
.push(
|
.push(
|
||||||
Text::new(format!("{:?}", background_color))
|
Text::new(format!("{background_color:?}"))
|
||||||
.size(14)
|
.size(14)
|
||||||
.style(Color::WHITE),
|
.style(Color::WHITE),
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -275,7 +275,7 @@ pub fn main() {
|
||||||
}
|
}
|
||||||
Err(error) => match error {
|
Err(error) => match error {
|
||||||
wgpu::SurfaceError::OutOfMemory => {
|
wgpu::SurfaceError::OutOfMemory => {
|
||||||
panic!("Swapchain error: {}. Rendering cannot continue.", error)
|
panic!("Swapchain error: {error}. Rendering cannot continue.")
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
// Try rendering again next frame.
|
// Try rendering again next frame.
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,7 @@ impl Application for Pokedex {
|
||||||
Pokedex::Errored { .. } => "Whoops!",
|
Pokedex::Errored { .. } => "Whoops!",
|
||||||
};
|
};
|
||||||
|
|
||||||
format!("{} - Pokédex", subtitle)
|
format!("{subtitle} - Pokédex")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update(&mut self, message: Message) -> Command<Message> {
|
fn update(&mut self, message: Message) -> Command<Message> {
|
||||||
|
|
@ -157,8 +157,7 @@ impl Pokemon {
|
||||||
};
|
};
|
||||||
|
|
||||||
let fetch_entry = async {
|
let fetch_entry = async {
|
||||||
let url =
|
let url = format!("https://pokeapi.co/api/v2/pokemon-species/{id}");
|
||||||
format!("https://pokeapi.co/api/v2/pokemon-species/{}", id);
|
|
||||||
|
|
||||||
reqwest::get(&url).await?.json().await
|
reqwest::get(&url).await?.json().await
|
||||||
};
|
};
|
||||||
|
|
@ -187,8 +186,7 @@ impl Pokemon {
|
||||||
|
|
||||||
async fn fetch_image(id: u16) -> Result<image::Handle, reqwest::Error> {
|
async fn fetch_image(id: u16) -> Result<image::Handle, reqwest::Error> {
|
||||||
let url = format!(
|
let url = format!(
|
||||||
"https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/{}.png",
|
"https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/{id}.png"
|
||||||
id
|
|
||||||
);
|
);
|
||||||
|
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
|
|
||||||
|
|
@ -78,7 +78,7 @@ impl Sandbox for Styling {
|
||||||
column![text("Choose a theme:")].spacing(10),
|
column![text("Choose a theme:")].spacing(10),
|
||||||
|column, theme| {
|
|column, theme| {
|
||||||
column.push(radio(
|
column.push(radio(
|
||||||
format!("{:?}", theme),
|
format!("{theme:?}"),
|
||||||
*theme,
|
*theme,
|
||||||
Some(match self.theme {
|
Some(match self.theme {
|
||||||
Theme::Light => ThemeType::Light,
|
Theme::Light => ThemeType::Light,
|
||||||
|
|
|
||||||
|
|
@ -114,13 +114,12 @@ impl Application for Example {
|
||||||
{
|
{
|
||||||
let memory_readable = ByteSize::kb(memory_used).to_string();
|
let memory_readable = ByteSize::kb(memory_used).to_string();
|
||||||
|
|
||||||
format!("{} kb ({})", memory_used, memory_readable)
|
format!("{memory_used} kb ({memory_readable})")
|
||||||
} else {
|
} else {
|
||||||
String::from("None")
|
String::from("None")
|
||||||
};
|
};
|
||||||
|
|
||||||
let memory_used =
|
let memory_used = text(format!("Memory (used): {memory_text}"));
|
||||||
text(format!("Memory (used): {}", memory_text));
|
|
||||||
|
|
||||||
let graphics_adapter = text(format!(
|
let graphics_adapter = text(format!(
|
||||||
"Graphics adapter: {}",
|
"Graphics adapter: {}",
|
||||||
|
|
|
||||||
10
examples/toast/Cargo.toml
Normal file
10
examples/toast/Cargo.toml
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
[package]
|
||||||
|
name = "toast"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["tarkah <admin@tarkah.dev>"]
|
||||||
|
edition = "2021"
|
||||||
|
publish = false
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
iced = { path = "../..", features = [] }
|
||||||
|
iced_native = { path = "../../native" }
|
||||||
670
examples/toast/src/main.rs
Normal file
670
examples/toast/src/main.rs
Normal file
|
|
@ -0,0 +1,670 @@
|
||||||
|
use iced::widget::{
|
||||||
|
self, button, column, container, pick_list, row, slider, text, text_input,
|
||||||
|
};
|
||||||
|
use iced::{
|
||||||
|
executor, keyboard, subscription, Alignment, Application, Command, Element,
|
||||||
|
Event, Length, Settings, Subscription,
|
||||||
|
};
|
||||||
|
|
||||||
|
use toast::{Status, Toast};
|
||||||
|
|
||||||
|
pub fn main() -> iced::Result {
|
||||||
|
App::run(Settings::default())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
struct App {
|
||||||
|
toasts: Vec<Toast>,
|
||||||
|
editing: Toast,
|
||||||
|
timeout_secs: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
#[allow(clippy::enum_variant_names)]
|
||||||
|
enum Message {
|
||||||
|
Add,
|
||||||
|
Close(usize),
|
||||||
|
Title(String),
|
||||||
|
Body(String),
|
||||||
|
Status(Status),
|
||||||
|
Timeout(f64),
|
||||||
|
Event(Event),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Application for App {
|
||||||
|
type Executor = executor::Default;
|
||||||
|
type Message = Message;
|
||||||
|
type Theme = iced::Theme;
|
||||||
|
type Flags = ();
|
||||||
|
|
||||||
|
fn new(_flags: ()) -> (Self, Command<Message>) {
|
||||||
|
(
|
||||||
|
App {
|
||||||
|
toasts: vec![Toast {
|
||||||
|
title: "Example Toast".into(),
|
||||||
|
body: "Add more toasts in the form below!".into(),
|
||||||
|
status: Status::Primary,
|
||||||
|
}],
|
||||||
|
timeout_secs: toast::DEFAULT_TIMEOUT,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
Command::none(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn title(&self) -> String {
|
||||||
|
String::from("Toast - Iced")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn subscription(&self) -> Subscription<Self::Message> {
|
||||||
|
subscription::events().map(Message::Event)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(&mut self, message: Message) -> Command<Message> {
|
||||||
|
match message {
|
||||||
|
Message::Add => {
|
||||||
|
if !self.editing.title.is_empty()
|
||||||
|
&& !self.editing.body.is_empty()
|
||||||
|
{
|
||||||
|
self.toasts.push(std::mem::take(&mut self.editing));
|
||||||
|
}
|
||||||
|
Command::none()
|
||||||
|
}
|
||||||
|
Message::Close(index) => {
|
||||||
|
self.toasts.remove(index);
|
||||||
|
Command::none()
|
||||||
|
}
|
||||||
|
Message::Title(title) => {
|
||||||
|
self.editing.title = title;
|
||||||
|
Command::none()
|
||||||
|
}
|
||||||
|
Message::Body(body) => {
|
||||||
|
self.editing.body = body;
|
||||||
|
Command::none()
|
||||||
|
}
|
||||||
|
Message::Status(status) => {
|
||||||
|
self.editing.status = status;
|
||||||
|
Command::none()
|
||||||
|
}
|
||||||
|
Message::Timeout(timeout) => {
|
||||||
|
self.timeout_secs = timeout as u64;
|
||||||
|
Command::none()
|
||||||
|
}
|
||||||
|
Message::Event(Event::Keyboard(keyboard::Event::KeyPressed {
|
||||||
|
key_code: keyboard::KeyCode::Tab,
|
||||||
|
modifiers,
|
||||||
|
})) if modifiers.shift() => widget::focus_previous(),
|
||||||
|
Message::Event(Event::Keyboard(keyboard::Event::KeyPressed {
|
||||||
|
key_code: keyboard::KeyCode::Tab,
|
||||||
|
..
|
||||||
|
})) => widget::focus_next(),
|
||||||
|
Message::Event(_) => Command::none(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn view<'a>(&'a self) -> Element<'a, Message> {
|
||||||
|
let subtitle = |title, content: Element<'a, Message>| {
|
||||||
|
column![text(title).size(14), content]
|
||||||
|
.width(Length::Fill)
|
||||||
|
.spacing(5)
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut add_toast = button("Add Toast");
|
||||||
|
|
||||||
|
if !self.editing.body.is_empty() && !self.editing.title.is_empty() {
|
||||||
|
add_toast = add_toast.on_press(Message::Add);
|
||||||
|
}
|
||||||
|
|
||||||
|
let content = container(
|
||||||
|
column![
|
||||||
|
subtitle(
|
||||||
|
"Title",
|
||||||
|
text_input("", &self.editing.title, Message::Title)
|
||||||
|
.on_submit(Message::Add)
|
||||||
|
.into()
|
||||||
|
),
|
||||||
|
subtitle(
|
||||||
|
"Message",
|
||||||
|
text_input("", &self.editing.body, Message::Body)
|
||||||
|
.on_submit(Message::Add)
|
||||||
|
.into()
|
||||||
|
),
|
||||||
|
subtitle(
|
||||||
|
"Status",
|
||||||
|
pick_list(
|
||||||
|
toast::Status::ALL,
|
||||||
|
Some(self.editing.status),
|
||||||
|
Message::Status
|
||||||
|
)
|
||||||
|
.width(Length::Fill)
|
||||||
|
.into()
|
||||||
|
),
|
||||||
|
subtitle(
|
||||||
|
"Timeout",
|
||||||
|
row![
|
||||||
|
text(format!("{:0>2} sec", self.timeout_secs)),
|
||||||
|
slider(
|
||||||
|
1.0..=30.0,
|
||||||
|
self.timeout_secs as f64,
|
||||||
|
Message::Timeout
|
||||||
|
)
|
||||||
|
.step(1.0)
|
||||||
|
.width(Length::Fill)
|
||||||
|
]
|
||||||
|
.spacing(5)
|
||||||
|
.into()
|
||||||
|
),
|
||||||
|
column![add_toast]
|
||||||
|
.width(Length::Fill)
|
||||||
|
.align_items(Alignment::End)
|
||||||
|
]
|
||||||
|
.spacing(10)
|
||||||
|
.max_width(200),
|
||||||
|
)
|
||||||
|
.width(Length::Fill)
|
||||||
|
.height(Length::Fill)
|
||||||
|
.center_x()
|
||||||
|
.center_y();
|
||||||
|
|
||||||
|
toast::Manager::new(content, &self.toasts, Message::Close)
|
||||||
|
.timeout(self.timeout_secs)
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mod toast {
|
||||||
|
use std::fmt;
|
||||||
|
use std::time::{Duration, Instant};
|
||||||
|
|
||||||
|
use iced::theme;
|
||||||
|
use iced::widget::{
|
||||||
|
button, column, container, horizontal_rule, horizontal_space, row, text,
|
||||||
|
};
|
||||||
|
use iced::{
|
||||||
|
Alignment, Element, Length, Point, Rectangle, Renderer, Size, Theme,
|
||||||
|
Vector,
|
||||||
|
};
|
||||||
|
use iced_native::widget::{tree, Operation, Tree};
|
||||||
|
use iced_native::{event, layout, mouse, overlay, renderer, window};
|
||||||
|
use iced_native::{Clipboard, Event, Layout, Shell, Widget};
|
||||||
|
|
||||||
|
pub const DEFAULT_TIMEOUT: u64 = 5;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
|
||||||
|
pub enum Status {
|
||||||
|
#[default]
|
||||||
|
Primary,
|
||||||
|
Secondary,
|
||||||
|
Success,
|
||||||
|
Danger,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Status {
|
||||||
|
pub const ALL: &[Self] =
|
||||||
|
&[Self::Primary, Self::Secondary, Self::Success, Self::Danger];
|
||||||
|
}
|
||||||
|
|
||||||
|
impl container::StyleSheet for Status {
|
||||||
|
type Style = Theme;
|
||||||
|
|
||||||
|
fn appearance(&self, theme: &Theme) -> container::Appearance {
|
||||||
|
let palette = theme.extended_palette();
|
||||||
|
|
||||||
|
let pair = match self {
|
||||||
|
Status::Primary => palette.primary.weak,
|
||||||
|
Status::Secondary => palette.secondary.weak,
|
||||||
|
Status::Success => palette.success.weak,
|
||||||
|
Status::Danger => palette.danger.weak,
|
||||||
|
};
|
||||||
|
|
||||||
|
container::Appearance {
|
||||||
|
background: pair.color.into(),
|
||||||
|
text_color: pair.text.into(),
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for Status {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
Status::Primary => "Primary",
|
||||||
|
Status::Secondary => "Secondary",
|
||||||
|
Status::Success => "Success",
|
||||||
|
Status::Danger => "Danger",
|
||||||
|
}
|
||||||
|
.fmt(f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Default)]
|
||||||
|
pub struct Toast {
|
||||||
|
pub title: String,
|
||||||
|
pub body: String,
|
||||||
|
pub status: Status,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Manager<'a, Message> {
|
||||||
|
content: Element<'a, Message>,
|
||||||
|
toasts: Vec<Element<'a, Message>>,
|
||||||
|
timeout_secs: u64,
|
||||||
|
on_close: Box<dyn Fn(usize) -> Message + 'a>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, Message> Manager<'a, Message>
|
||||||
|
where
|
||||||
|
Message: 'a + Clone,
|
||||||
|
{
|
||||||
|
pub fn new(
|
||||||
|
content: impl Into<Element<'a, Message>>,
|
||||||
|
toasts: &'a [Toast],
|
||||||
|
on_close: impl Fn(usize) -> Message + 'a,
|
||||||
|
) -> Self {
|
||||||
|
let toasts = toasts
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(index, toast)| {
|
||||||
|
container(column![
|
||||||
|
container(
|
||||||
|
row![
|
||||||
|
text(toast.title.as_str()),
|
||||||
|
horizontal_space(Length::Fill),
|
||||||
|
button("X")
|
||||||
|
.on_press((on_close)(index))
|
||||||
|
.padding(3),
|
||||||
|
]
|
||||||
|
.align_items(Alignment::Center)
|
||||||
|
)
|
||||||
|
.width(Length::Fill)
|
||||||
|
.padding(5)
|
||||||
|
.style(
|
||||||
|
theme::Container::Custom(Box::new(toast.status))
|
||||||
|
),
|
||||||
|
horizontal_rule(1),
|
||||||
|
container(text(toast.body.as_str()))
|
||||||
|
.width(Length::Fill)
|
||||||
|
.padding(5)
|
||||||
|
.style(theme::Container::Box),
|
||||||
|
])
|
||||||
|
.max_width(200)
|
||||||
|
.into()
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
Self {
|
||||||
|
content: content.into(),
|
||||||
|
toasts,
|
||||||
|
timeout_secs: DEFAULT_TIMEOUT,
|
||||||
|
on_close: Box::new(on_close),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn timeout(self, seconds: u64) -> Self {
|
||||||
|
Self {
|
||||||
|
timeout_secs: seconds,
|
||||||
|
..self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, Message> Widget<Message, Renderer> for Manager<'a, Message> {
|
||||||
|
fn width(&self) -> Length {
|
||||||
|
self.content.as_widget().width()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn height(&self) -> Length {
|
||||||
|
self.content.as_widget().height()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn layout(
|
||||||
|
&self,
|
||||||
|
renderer: &Renderer,
|
||||||
|
limits: &layout::Limits,
|
||||||
|
) -> layout::Node {
|
||||||
|
self.content.as_widget().layout(renderer, limits)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn tag(&self) -> tree::Tag {
|
||||||
|
struct Marker(Vec<Instant>);
|
||||||
|
iced_native::widget::tree::Tag::of::<Marker>()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn state(&self) -> tree::State {
|
||||||
|
iced_native::widget::tree::State::new(Vec::<Option<Instant>>::new())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn children(&self) -> Vec<Tree> {
|
||||||
|
std::iter::once(Tree::new(&self.content))
|
||||||
|
.chain(self.toasts.iter().map(Tree::new))
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn diff(&self, tree: &mut Tree) {
|
||||||
|
let instants = tree.state.downcast_mut::<Vec<Option<Instant>>>();
|
||||||
|
|
||||||
|
// Invalidating removed instants to None allows us to remove
|
||||||
|
// them here so that diffing for removed / new toast instants
|
||||||
|
// is accurate
|
||||||
|
instants.retain(Option::is_some);
|
||||||
|
|
||||||
|
match (instants.len(), self.toasts.len()) {
|
||||||
|
(old, new) if old > new => {
|
||||||
|
instants.truncate(new);
|
||||||
|
}
|
||||||
|
(old, new) if old < new => {
|
||||||
|
instants.extend(
|
||||||
|
std::iter::repeat(Some(Instant::now())).take(new - old),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
tree.diff_children(
|
||||||
|
&std::iter::once(&self.content)
|
||||||
|
.chain(self.toasts.iter())
|
||||||
|
.collect::<Vec<_>>(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn operate(
|
||||||
|
&self,
|
||||||
|
state: &mut Tree,
|
||||||
|
layout: Layout<'_>,
|
||||||
|
renderer: &Renderer,
|
||||||
|
operation: &mut dyn Operation<Message>,
|
||||||
|
) {
|
||||||
|
operation.container(None, &mut |operation| {
|
||||||
|
self.content.as_widget().operate(
|
||||||
|
&mut state.children[0],
|
||||||
|
layout,
|
||||||
|
renderer,
|
||||||
|
operation,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_event(
|
||||||
|
&mut self,
|
||||||
|
state: &mut Tree,
|
||||||
|
event: Event,
|
||||||
|
layout: Layout<'_>,
|
||||||
|
cursor_position: Point,
|
||||||
|
renderer: &Renderer,
|
||||||
|
clipboard: &mut dyn Clipboard,
|
||||||
|
shell: &mut Shell<'_, Message>,
|
||||||
|
) -> event::Status {
|
||||||
|
self.content.as_widget_mut().on_event(
|
||||||
|
&mut state.children[0],
|
||||||
|
event,
|
||||||
|
layout,
|
||||||
|
cursor_position,
|
||||||
|
renderer,
|
||||||
|
clipboard,
|
||||||
|
shell,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw(
|
||||||
|
&self,
|
||||||
|
state: &Tree,
|
||||||
|
renderer: &mut Renderer,
|
||||||
|
theme: &Theme,
|
||||||
|
style: &renderer::Style,
|
||||||
|
layout: Layout<'_>,
|
||||||
|
cursor_position: Point,
|
||||||
|
viewport: &Rectangle,
|
||||||
|
) {
|
||||||
|
self.content.as_widget().draw(
|
||||||
|
&state.children[0],
|
||||||
|
renderer,
|
||||||
|
theme,
|
||||||
|
style,
|
||||||
|
layout,
|
||||||
|
cursor_position,
|
||||||
|
viewport,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn mouse_interaction(
|
||||||
|
&self,
|
||||||
|
state: &Tree,
|
||||||
|
layout: Layout<'_>,
|
||||||
|
cursor_position: Point,
|
||||||
|
viewport: &Rectangle,
|
||||||
|
renderer: &Renderer,
|
||||||
|
) -> mouse::Interaction {
|
||||||
|
self.content.as_widget().mouse_interaction(
|
||||||
|
&state.children[0],
|
||||||
|
layout,
|
||||||
|
cursor_position,
|
||||||
|
viewport,
|
||||||
|
renderer,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn overlay<'b>(
|
||||||
|
&'b mut self,
|
||||||
|
state: &'b mut Tree,
|
||||||
|
layout: Layout<'_>,
|
||||||
|
renderer: &Renderer,
|
||||||
|
) -> Option<overlay::Element<'b, Message, Renderer>> {
|
||||||
|
let instants = state.state.downcast_mut::<Vec<Option<Instant>>>();
|
||||||
|
|
||||||
|
let (content_state, toasts_state) = state.children.split_at_mut(1);
|
||||||
|
|
||||||
|
let content = self.content.as_widget_mut().overlay(
|
||||||
|
&mut content_state[0],
|
||||||
|
layout,
|
||||||
|
renderer,
|
||||||
|
);
|
||||||
|
|
||||||
|
let toasts = (!self.toasts.is_empty()).then(|| {
|
||||||
|
overlay::Element::new(
|
||||||
|
layout.bounds().position(),
|
||||||
|
Box::new(Overlay {
|
||||||
|
toasts: &mut self.toasts,
|
||||||
|
state: toasts_state,
|
||||||
|
instants,
|
||||||
|
on_close: &self.on_close,
|
||||||
|
timeout_secs: self.timeout_secs,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
});
|
||||||
|
let overlays =
|
||||||
|
content.into_iter().chain(toasts).collect::<Vec<_>>();
|
||||||
|
|
||||||
|
(!overlays.is_empty())
|
||||||
|
.then(|| overlay::Group::with_children(overlays).overlay())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Overlay<'a, 'b, Message> {
|
||||||
|
toasts: &'b mut [Element<'a, Message>],
|
||||||
|
state: &'b mut [Tree],
|
||||||
|
instants: &'b mut [Option<Instant>],
|
||||||
|
on_close: &'b dyn Fn(usize) -> Message,
|
||||||
|
timeout_secs: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, 'b, Message> overlay::Overlay<Message, Renderer>
|
||||||
|
for Overlay<'a, 'b, Message>
|
||||||
|
{
|
||||||
|
fn layout(
|
||||||
|
&self,
|
||||||
|
renderer: &Renderer,
|
||||||
|
bounds: Size,
|
||||||
|
position: Point,
|
||||||
|
) -> layout::Node {
|
||||||
|
let limits = layout::Limits::new(Size::ZERO, bounds)
|
||||||
|
.width(Length::Fill)
|
||||||
|
.height(Length::Fill);
|
||||||
|
|
||||||
|
layout::flex::resolve(
|
||||||
|
layout::flex::Axis::Vertical,
|
||||||
|
renderer,
|
||||||
|
&limits,
|
||||||
|
10.into(),
|
||||||
|
10.0,
|
||||||
|
Alignment::End,
|
||||||
|
self.toasts,
|
||||||
|
)
|
||||||
|
.translate(Vector::new(position.x, position.y))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_event(
|
||||||
|
&mut self,
|
||||||
|
event: Event,
|
||||||
|
layout: Layout<'_>,
|
||||||
|
cursor_position: Point,
|
||||||
|
renderer: &Renderer,
|
||||||
|
clipboard: &mut dyn Clipboard,
|
||||||
|
shell: &mut Shell<'_, Message>,
|
||||||
|
) -> event::Status {
|
||||||
|
if let Event::Window(window::Event::RedrawRequested(now)) = &event {
|
||||||
|
let mut next_redraw: Option<window::RedrawRequest> = None;
|
||||||
|
|
||||||
|
self.instants.iter_mut().enumerate().for_each(
|
||||||
|
|(index, maybe_instant)| {
|
||||||
|
if let Some(instant) = maybe_instant.as_mut() {
|
||||||
|
let remaining =
|
||||||
|
Duration::from_secs(self.timeout_secs)
|
||||||
|
.saturating_sub(instant.elapsed());
|
||||||
|
|
||||||
|
if remaining == Duration::ZERO {
|
||||||
|
maybe_instant.take();
|
||||||
|
shell.publish((self.on_close)(index));
|
||||||
|
next_redraw =
|
||||||
|
Some(window::RedrawRequest::NextFrame);
|
||||||
|
} else {
|
||||||
|
let redraw_at =
|
||||||
|
window::RedrawRequest::At(*now + remaining);
|
||||||
|
next_redraw = next_redraw
|
||||||
|
.map(|redraw| redraw.min(redraw_at))
|
||||||
|
.or(Some(redraw_at));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
if let Some(redraw) = next_redraw {
|
||||||
|
shell.request_redraw(redraw);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.toasts
|
||||||
|
.iter_mut()
|
||||||
|
.zip(self.state.iter_mut())
|
||||||
|
.zip(layout.children())
|
||||||
|
.zip(self.instants.iter_mut())
|
||||||
|
.map(|(((child, state), layout), instant)| {
|
||||||
|
let mut local_messages = vec![];
|
||||||
|
let mut local_shell = Shell::new(&mut local_messages);
|
||||||
|
|
||||||
|
let status = child.as_widget_mut().on_event(
|
||||||
|
state,
|
||||||
|
event.clone(),
|
||||||
|
layout,
|
||||||
|
cursor_position,
|
||||||
|
renderer,
|
||||||
|
clipboard,
|
||||||
|
&mut local_shell,
|
||||||
|
);
|
||||||
|
|
||||||
|
if !local_shell.is_empty() {
|
||||||
|
instant.take();
|
||||||
|
}
|
||||||
|
|
||||||
|
shell.merge(local_shell, std::convert::identity);
|
||||||
|
|
||||||
|
status
|
||||||
|
})
|
||||||
|
.fold(event::Status::Ignored, event::Status::merge)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw(
|
||||||
|
&self,
|
||||||
|
renderer: &mut Renderer,
|
||||||
|
theme: &<Renderer as iced_native::Renderer>::Theme,
|
||||||
|
style: &renderer::Style,
|
||||||
|
layout: Layout<'_>,
|
||||||
|
cursor_position: Point,
|
||||||
|
) {
|
||||||
|
let viewport = layout.bounds();
|
||||||
|
|
||||||
|
for ((child, state), layout) in self
|
||||||
|
.toasts
|
||||||
|
.iter()
|
||||||
|
.zip(self.state.iter())
|
||||||
|
.zip(layout.children())
|
||||||
|
{
|
||||||
|
child.as_widget().draw(
|
||||||
|
state,
|
||||||
|
renderer,
|
||||||
|
theme,
|
||||||
|
style,
|
||||||
|
layout,
|
||||||
|
cursor_position,
|
||||||
|
&viewport,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn operate(
|
||||||
|
&mut self,
|
||||||
|
layout: Layout<'_>,
|
||||||
|
renderer: &Renderer,
|
||||||
|
operation: &mut dyn iced_native::widget::Operation<Message>,
|
||||||
|
) {
|
||||||
|
operation.container(None, &mut |operation| {
|
||||||
|
self.toasts
|
||||||
|
.iter()
|
||||||
|
.zip(self.state.iter_mut())
|
||||||
|
.zip(layout.children())
|
||||||
|
.for_each(|((child, state), layout)| {
|
||||||
|
child
|
||||||
|
.as_widget()
|
||||||
|
.operate(state, layout, renderer, operation);
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn mouse_interaction(
|
||||||
|
&self,
|
||||||
|
layout: Layout<'_>,
|
||||||
|
cursor_position: Point,
|
||||||
|
viewport: &Rectangle,
|
||||||
|
renderer: &Renderer,
|
||||||
|
) -> mouse::Interaction {
|
||||||
|
self.toasts
|
||||||
|
.iter()
|
||||||
|
.zip(self.state.iter())
|
||||||
|
.zip(layout.children())
|
||||||
|
.map(|((child, state), layout)| {
|
||||||
|
child.as_widget().mouse_interaction(
|
||||||
|
state,
|
||||||
|
layout,
|
||||||
|
cursor_position,
|
||||||
|
viewport,
|
||||||
|
renderer,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.max()
|
||||||
|
.unwrap_or_default()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_over(&self, layout: Layout<'_>, cursor_position: Point) -> bool {
|
||||||
|
layout
|
||||||
|
.children()
|
||||||
|
.any(|layout| layout.bounds().contains(cursor_position))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, Message> From<Manager<'a, Message>> for Element<'a, Message>
|
||||||
|
where
|
||||||
|
Message: 'a,
|
||||||
|
{
|
||||||
|
fn from(manager: Manager<'a, Message>) -> Self {
|
||||||
|
Element::new(manager)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -303,7 +303,7 @@ pub enum TaskMessage {
|
||||||
|
|
||||||
impl Task {
|
impl Task {
|
||||||
fn text_input_id(i: usize) -> text_input::Id {
|
fn text_input_id(i: usize) -> text_input::Id {
|
||||||
text_input::Id::new(format!("task-{}", i))
|
text_input::Id::new(format!("task-{i}"))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn new(description: String) -> Self {
|
fn new(description: String) -> Self {
|
||||||
|
|
|
||||||
|
|
@ -388,7 +388,7 @@ impl<'a> Step {
|
||||||
|
|
||||||
let spacing_section = column![
|
let spacing_section = column![
|
||||||
slider(0..=80, spacing, StepMessage::SpacingChanged),
|
slider(0..=80, spacing, StepMessage::SpacingChanged),
|
||||||
text(format!("{} px", spacing))
|
text(format!("{spacing} px"))
|
||||||
.width(Length::Fill)
|
.width(Length::Fill)
|
||||||
.horizontal_alignment(alignment::Horizontal::Center),
|
.horizontal_alignment(alignment::Horizontal::Center),
|
||||||
]
|
]
|
||||||
|
|
@ -412,7 +412,7 @@ impl<'a> Step {
|
||||||
fn text(size: u16, color: Color) -> Column<'a, StepMessage> {
|
fn text(size: u16, color: Color) -> Column<'a, StepMessage> {
|
||||||
let size_section = column![
|
let size_section = column![
|
||||||
"You can change its size:",
|
"You can change its size:",
|
||||||
text(format!("This text is {} pixels", size)).size(size),
|
text(format!("This text is {size} pixels")).size(size),
|
||||||
slider(10..=70, size, StepMessage::TextSizeChanged),
|
slider(10..=70, size, StepMessage::TextSizeChanged),
|
||||||
]
|
]
|
||||||
.padding(20)
|
.padding(20)
|
||||||
|
|
@ -427,7 +427,7 @@ impl<'a> Step {
|
||||||
|
|
||||||
let color_section = column![
|
let color_section = column![
|
||||||
"And its color:",
|
"And its color:",
|
||||||
text(format!("{:?}", color)).style(color),
|
text(format!("{color:?}")).style(color),
|
||||||
color_sliders,
|
color_sliders,
|
||||||
]
|
]
|
||||||
.padding(20)
|
.padding(20)
|
||||||
|
|
@ -497,7 +497,7 @@ impl<'a> Step {
|
||||||
.push(ferris(width))
|
.push(ferris(width))
|
||||||
.push(slider(100..=500, width, StepMessage::ImageWidthChanged))
|
.push(slider(100..=500, width, StepMessage::ImageWidthChanged))
|
||||||
.push(
|
.push(
|
||||||
text(format!("Width: {} px", width))
|
text(format!("Width: {width} px"))
|
||||||
.width(Length::Fill)
|
.width(Length::Fill)
|
||||||
.horizontal_alignment(alignment::Horizontal::Center),
|
.horizontal_alignment(alignment::Horizontal::Center),
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -141,7 +141,7 @@ impl fmt::Display for Message {
|
||||||
Message::Disconnected => {
|
Message::Disconnected => {
|
||||||
write!(f, "Connection lost... Retrying...")
|
write!(f, "Connection lost... Retrying...")
|
||||||
}
|
}
|
||||||
Message::User(message) => write!(f, "{}", message),
|
Message::User(message) => write!(f, "{message}"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,7 @@ async fn user_connected(ws: WebSocket) {
|
||||||
tokio::task::spawn(async move {
|
tokio::task::spawn(async move {
|
||||||
while let Some(message) = rx.next().await {
|
while let Some(message) = rx.next().await {
|
||||||
user_ws_tx.send(message).await.unwrap_or_else(|e| {
|
user_ws_tx.send(message).await.unwrap_or_else(|e| {
|
||||||
eprintln!("websocket send error: {}", e);
|
eprintln!("websocket send error: {e}");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ impl crate::Executor for Executor {
|
||||||
Ok(Self)
|
Ok(Self)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::let_underscore_future)]
|
||||||
fn spawn(&self, future: impl Future<Output = ()> + Send + 'static) {
|
fn spawn(&self, future: impl Future<Output = ()> + Send + 'static) {
|
||||||
let _ = async_std::task::spawn(future);
|
let _ = async_std::task::spawn(future);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ impl crate::Executor for Executor {
|
||||||
tokio::runtime::Runtime::new()
|
tokio::runtime::Runtime::new()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::let_underscore_future)]
|
||||||
fn spawn(&self, future: impl Future<Output = ()> + Send + 'static) {
|
fn spawn(&self, future: impl Future<Output = ()> + Send + 'static) {
|
||||||
let _ = tokio::runtime::Runtime::spawn(self, future);
|
let _ = tokio::runtime::Runtime::spawn(self, future);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
/// A set of asynchronous actions to be performed by some runtime.
|
/// A set of asynchronous actions to be performed by some runtime.
|
||||||
|
#[must_use = "`Command` must be returned to runtime to take effect"]
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Command<T>(Internal<T>);
|
pub struct Command<T>(Internal<T>);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ use crate::BoxStream;
|
||||||
/// `Hasher`.
|
/// `Hasher`.
|
||||||
///
|
///
|
||||||
/// [`Command`]: crate::Command
|
/// [`Command`]: crate::Command
|
||||||
|
#[must_use = "`Subscription` must be returned to runtime to take effect"]
|
||||||
pub struct Subscription<Hasher, Event, Output> {
|
pub struct Subscription<Hasher, Event, Output> {
|
||||||
recipes: Vec<Box<dyn Recipe<Hasher, Event, Output = Output>>>,
|
recipes: Vec<Box<dyn Recipe<Hasher, Event, Output = Output>>>,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -54,7 +54,7 @@ impl Version {
|
||||||
String::from("#version 120\n#define in varying"),
|
String::from("#version 120\n#define in varying"),
|
||||||
),
|
),
|
||||||
// OpenGL 1.1+
|
// OpenGL 1.1+
|
||||||
_ => panic!("Incompatible context version: {:?}", version),
|
_ => panic!("Incompatible context version: {version:?}"),
|
||||||
};
|
};
|
||||||
|
|
||||||
log::info!("Shader directive: {}", vertex.lines().next().unwrap());
|
log::info!("Shader directive: {}", vertex.lines().next().unwrap());
|
||||||
|
|
|
||||||
|
|
@ -53,9 +53,9 @@ where
|
||||||
{
|
{
|
||||||
type Theme = T;
|
type Theme = T;
|
||||||
|
|
||||||
fn layout<'a, Message>(
|
fn layout<Message>(
|
||||||
&mut self,
|
&mut self,
|
||||||
element: &Element<'a, Message, Self>,
|
element: &Element<'_, Message, Self>,
|
||||||
limits: &layout::Limits,
|
limits: &layout::Limits,
|
||||||
) -> layout::Node {
|
) -> layout::Node {
|
||||||
let layout = element.as_widget().layout(self, limits);
|
let layout = element.as_widget().layout(self, limits);
|
||||||
|
|
|
||||||
|
|
@ -49,7 +49,11 @@ impl Cache {
|
||||||
/// Otherwise, the previously stored [`Geometry`] will be returned. The
|
/// Otherwise, the previously stored [`Geometry`] will be returned. The
|
||||||
/// [`Cache`] is not cleared in this case. In other words, it will keep
|
/// [`Cache`] is not cleared in this case. In other words, it will keep
|
||||||
/// returning the stored [`Geometry`] if needed.
|
/// returning the stored [`Geometry`] if needed.
|
||||||
pub fn draw(&self, bounds: Size, draw_fn: impl Fn(&mut Frame)) -> Geometry {
|
pub fn draw(
|
||||||
|
&self,
|
||||||
|
bounds: Size,
|
||||||
|
draw_fn: impl FnOnce(&mut Frame),
|
||||||
|
) -> Geometry {
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
|
|
||||||
if let State::Filled {
|
if let State::Filled {
|
||||||
|
|
|
||||||
|
|
@ -227,6 +227,10 @@ where
|
||||||
|
|
||||||
local_shell.revalidate_layout(|| shell.invalidate_layout());
|
local_shell.revalidate_layout(|| shell.invalidate_layout());
|
||||||
|
|
||||||
|
if let Some(redraw_request) = local_shell.redraw_request() {
|
||||||
|
shell.request_redraw(redraw_request);
|
||||||
|
}
|
||||||
|
|
||||||
if !local_messages.is_empty() {
|
if !local_messages.is_empty() {
|
||||||
let mut heads = self.state.take().unwrap().into_heads();
|
let mut heads = self.state.take().unwrap().into_heads();
|
||||||
|
|
||||||
|
|
@ -307,6 +311,8 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
self.with_element(|element| {
|
self.with_element(|element| {
|
||||||
|
tree.diff_children(std::slice::from_ref(&element));
|
||||||
|
|
||||||
element.as_widget().operate(
|
element.as_widget().operate(
|
||||||
&mut tree.children[0],
|
&mut tree.children[0],
|
||||||
layout,
|
layout,
|
||||||
|
|
@ -451,9 +457,9 @@ where
|
||||||
position: Point,
|
position: Point,
|
||||||
) -> layout::Node {
|
) -> layout::Node {
|
||||||
self.with_overlay_maybe(|overlay| {
|
self.with_overlay_maybe(|overlay| {
|
||||||
let vector = position - overlay.position();
|
let translation = position - overlay.position();
|
||||||
|
|
||||||
overlay.layout(renderer, bounds).translate(vector)
|
overlay.layout(renderer, bounds, translation)
|
||||||
})
|
})
|
||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
}
|
}
|
||||||
|
|
@ -559,4 +565,11 @@ where
|
||||||
|
|
||||||
event_status
|
event_status
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn is_over(&self, layout: Layout<'_>, cursor_position: Point) -> bool {
|
||||||
|
self.with_overlay_maybe(|overlay| {
|
||||||
|
overlay.is_over(layout, cursor_position)
|
||||||
|
})
|
||||||
|
.unwrap_or_default()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -313,9 +313,9 @@ where
|
||||||
position: Point,
|
position: Point,
|
||||||
) -> layout::Node {
|
) -> layout::Node {
|
||||||
self.with_overlay_maybe(|overlay| {
|
self.with_overlay_maybe(|overlay| {
|
||||||
let vector = position - overlay.position();
|
let translation = position - overlay.position();
|
||||||
|
|
||||||
overlay.layout(renderer, bounds).translate(vector)
|
overlay.layout(renderer, bounds, translation)
|
||||||
})
|
})
|
||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
}
|
}
|
||||||
|
|
@ -372,6 +372,13 @@ where
|
||||||
})
|
})
|
||||||
.unwrap_or(iced_native::event::Status::Ignored)
|
.unwrap_or(iced_native::event::Status::Ignored)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn is_over(&self, layout: Layout<'_>, cursor_position: Point) -> bool {
|
||||||
|
self.with_overlay_maybe(|overlay| {
|
||||||
|
overlay.is_over(layout, cursor_position)
|
||||||
|
})
|
||||||
|
.unwrap_or_default()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, Message, Renderer, Dependency, View>
|
impl<'a, Message, Renderer, Dependency, View>
|
||||||
|
|
|
||||||
|
|
@ -356,9 +356,9 @@ where
|
||||||
position: Point,
|
position: Point,
|
||||||
) -> layout::Node {
|
) -> layout::Node {
|
||||||
self.with_overlay_maybe(|overlay| {
|
self.with_overlay_maybe(|overlay| {
|
||||||
let vector = position - overlay.position();
|
let translation = position - overlay.position();
|
||||||
|
|
||||||
overlay.layout(renderer, bounds).translate(vector)
|
overlay.layout(renderer, bounds, translation)
|
||||||
})
|
})
|
||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
}
|
}
|
||||||
|
|
@ -415,4 +415,11 @@ where
|
||||||
})
|
})
|
||||||
.unwrap_or(iced_native::event::Status::Ignored)
|
.unwrap_or(iced_native::event::Status::Ignored)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn is_over(&self, layout: Layout<'_>, cursor_position: Point) -> bool {
|
||||||
|
self.with_overlay_maybe(|overlay| {
|
||||||
|
overlay.is_over(layout, cursor_position)
|
||||||
|
})
|
||||||
|
.unwrap_or_default()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ use std::fmt;
|
||||||
use std::future::Future;
|
use std::future::Future;
|
||||||
|
|
||||||
/// A set of asynchronous actions to be performed by some runtime.
|
/// A set of asynchronous actions to be performed by some runtime.
|
||||||
|
#[must_use = "`Command` must be returned to runtime to take effect"]
|
||||||
pub struct Command<T>(iced_futures::Command<Action<T>>);
|
pub struct Command<T>(iced_futures::Command<Action<T>>);
|
||||||
|
|
||||||
impl<T> Command<T> {
|
impl<T> Command<T> {
|
||||||
|
|
|
||||||
|
|
@ -58,12 +58,12 @@ impl<T> fmt::Debug for Action<T> {
|
||||||
match self {
|
match self {
|
||||||
Self::Future(_) => write!(f, "Action::Future"),
|
Self::Future(_) => write!(f, "Action::Future"),
|
||||||
Self::Clipboard(action) => {
|
Self::Clipboard(action) => {
|
||||||
write!(f, "Action::Clipboard({:?})", action)
|
write!(f, "Action::Clipboard({action:?})")
|
||||||
}
|
}
|
||||||
Self::Window(id, action) => {
|
Self::Window(id, action) => {
|
||||||
write!(f, "Action::Window({:?}, {:?})", id, action)
|
write!(f, "Action::Window({id:?}, {action:?})")
|
||||||
}
|
}
|
||||||
Self::System(action) => write!(f, "Action::System({:?})", action),
|
Self::System(action) => write!(f, "Action::System({action:?})"),
|
||||||
Self::Widget(_action) => write!(f, "Action::Widget"),
|
Self::Widget(_action) => write!(f, "Action::Widget"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -133,7 +133,7 @@ impl Debug {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn log_message<Message: std::fmt::Debug>(&mut self, message: &Message) {
|
pub fn log_message<Message: std::fmt::Debug>(&mut self, message: &Message) {
|
||||||
self.last_messages.push_back(format!("{:?}", message));
|
self.last_messages.push_back(format!("{message:?}"));
|
||||||
|
|
||||||
if self.last_messages.len() > 10 {
|
if self.last_messages.len() > 10 {
|
||||||
let _ = self.last_messages.pop_front();
|
let _ = self.last_messages.pop_front();
|
||||||
|
|
@ -150,7 +150,7 @@ impl Debug {
|
||||||
let mut lines = Vec::new();
|
let mut lines = Vec::new();
|
||||||
|
|
||||||
fn key_value<T: std::fmt::Debug>(key: &str, value: T) -> String {
|
fn key_value<T: std::fmt::Debug>(key: &str, value: T) -> String {
|
||||||
format!("{} {:?}", key, value)
|
format!("{key} {value:?}")
|
||||||
}
|
}
|
||||||
|
|
||||||
lines.push(format!(
|
lines.push(format!(
|
||||||
|
|
@ -176,9 +176,9 @@ impl Debug {
|
||||||
lines.push(String::from("Last messages:"));
|
lines.push(String::from("Last messages:"));
|
||||||
lines.extend(self.last_messages.iter().map(|msg| {
|
lines.extend(self.last_messages.iter().map(|msg| {
|
||||||
if msg.len() <= 100 {
|
if msg.len() <= 100 {
|
||||||
format!(" {}", msg)
|
format!(" {msg}")
|
||||||
} else {
|
} else {
|
||||||
format!(" {:.100}...", msg)
|
format!(" {msg:.100}...")
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -107,10 +107,10 @@ pub enum Data {
|
||||||
impl std::fmt::Debug for Data {
|
impl std::fmt::Debug for Data {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
Data::Path(path) => write!(f, "Path({:?})", path),
|
Data::Path(path) => write!(f, "Path({path:?})"),
|
||||||
Data::Bytes(_) => write!(f, "Bytes(...)"),
|
Data::Bytes(_) => write!(f, "Bytes(...)"),
|
||||||
Data::Rgba { width, height, .. } => {
|
Data::Rgba { width, height, .. } => {
|
||||||
write!(f, "Pixels({} * {})", width, height)
|
write!(f, "Pixels({width} * {height})")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,11 @@
|
||||||
//! Display interactive elements on top of other widgets.
|
//! Display interactive elements on top of other widgets.
|
||||||
mod element;
|
mod element;
|
||||||
|
mod group;
|
||||||
|
|
||||||
pub mod menu;
|
pub mod menu;
|
||||||
|
|
||||||
pub use element::Element;
|
pub use element::Element;
|
||||||
|
pub use group::Group;
|
||||||
pub use menu::Menu;
|
pub use menu::Menu;
|
||||||
|
|
||||||
use crate::event::{self, Event};
|
use crate::event::{self, Event};
|
||||||
|
|
@ -87,9 +89,17 @@ where
|
||||||
) -> mouse::Interaction {
|
) -> mouse::Interaction {
|
||||||
mouse::Interaction::Idle
|
mouse::Interaction::Idle
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns true if the cursor is over the [`Overlay`].
|
||||||
|
///
|
||||||
|
/// By default, it returns true if the bounds of the `layout` contain
|
||||||
|
/// the `cursor_position`.
|
||||||
|
fn is_over(&self, layout: Layout<'_>, cursor_position: Point) -> bool {
|
||||||
|
layout.bounds().contains(cursor_position)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Obtains the first overlay [`Element`] found in the given children.
|
/// Returns a [`Group`] of overlay [`Element`] children.
|
||||||
///
|
///
|
||||||
/// This method will generally only be used by advanced users that are
|
/// This method will generally only be used by advanced users that are
|
||||||
/// implementing the [`Widget`](crate::Widget) trait.
|
/// implementing the [`Widget`](crate::Widget) trait.
|
||||||
|
|
@ -102,12 +112,14 @@ pub fn from_children<'a, Message, Renderer>(
|
||||||
where
|
where
|
||||||
Renderer: crate::Renderer,
|
Renderer: crate::Renderer,
|
||||||
{
|
{
|
||||||
children
|
let children = children
|
||||||
.iter_mut()
|
.iter_mut()
|
||||||
.zip(&mut tree.children)
|
.zip(&mut tree.children)
|
||||||
.zip(layout.children())
|
.zip(layout.children())
|
||||||
.filter_map(|((child, state), layout)| {
|
.filter_map(|((child, state), layout)| {
|
||||||
child.as_widget_mut().overlay(state, layout, renderer)
|
child.as_widget_mut().overlay(state, layout, renderer)
|
||||||
})
|
})
|
||||||
.next()
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
(!children.is_empty()).then(|| Group::with_children(children).overlay())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -53,8 +53,14 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Computes the layout of the [`Element`] in the given bounds.
|
/// Computes the layout of the [`Element`] in the given bounds.
|
||||||
pub fn layout(&self, renderer: &Renderer, bounds: Size) -> layout::Node {
|
pub fn layout(
|
||||||
self.overlay.layout(renderer, bounds, self.position)
|
&self,
|
||||||
|
renderer: &Renderer,
|
||||||
|
bounds: Size,
|
||||||
|
translation: Vector,
|
||||||
|
) -> layout::Node {
|
||||||
|
self.overlay
|
||||||
|
.layout(renderer, bounds, self.position + translation)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Processes a runtime [`Event`].
|
/// Processes a runtime [`Event`].
|
||||||
|
|
@ -115,6 +121,11 @@ where
|
||||||
) {
|
) {
|
||||||
self.overlay.operate(layout, renderer, operation);
|
self.overlay.operate(layout, renderer, operation);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns true if the cursor is over the [`Element`].
|
||||||
|
pub fn is_over(&self, layout: Layout<'_>, cursor_position: Point) -> bool {
|
||||||
|
self.overlay.is_over(layout, cursor_position)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Map<'a, A, B, Renderer> {
|
struct Map<'a, A, B, Renderer> {
|
||||||
|
|
@ -252,4 +263,8 @@ where
|
||||||
self.content
|
self.content
|
||||||
.draw(renderer, theme, style, layout, cursor_position)
|
.draw(renderer, theme, style, layout, cursor_position)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn is_over(&self, layout: Layout<'_>, cursor_position: Point) -> bool {
|
||||||
|
self.content.is_over(layout, cursor_position)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
174
native/src/overlay/group.rs
Normal file
174
native/src/overlay/group.rs
Normal file
|
|
@ -0,0 +1,174 @@
|
||||||
|
use iced_core::{Point, Rectangle, Size};
|
||||||
|
|
||||||
|
use crate::event;
|
||||||
|
use crate::layout;
|
||||||
|
use crate::mouse;
|
||||||
|
use crate::overlay;
|
||||||
|
use crate::renderer;
|
||||||
|
use crate::widget;
|
||||||
|
use crate::{Clipboard, Event, Layout, Overlay, Shell};
|
||||||
|
|
||||||
|
/// An [`Overlay`] container that displays multiple overlay [`overlay::Element`]
|
||||||
|
/// children.
|
||||||
|
#[allow(missing_debug_implementations)]
|
||||||
|
pub struct Group<'a, Message, Renderer> {
|
||||||
|
children: Vec<overlay::Element<'a, Message, Renderer>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, Message, Renderer> Group<'a, Message, Renderer>
|
||||||
|
where
|
||||||
|
Renderer: 'a + crate::Renderer,
|
||||||
|
Message: 'a,
|
||||||
|
{
|
||||||
|
/// Creates an empty [`Group`].
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self::default()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a [`Group`] with the given elements.
|
||||||
|
pub fn with_children(
|
||||||
|
children: Vec<overlay::Element<'a, Message, Renderer>>,
|
||||||
|
) -> Self {
|
||||||
|
Group { children }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Adds an [`overlay::Element`] to the [`Group`].
|
||||||
|
pub fn push(
|
||||||
|
mut self,
|
||||||
|
child: impl Into<overlay::Element<'a, Message, Renderer>>,
|
||||||
|
) -> Self {
|
||||||
|
self.children.push(child.into());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Turns the [`Group`] into an overlay [`overlay::Element`].
|
||||||
|
pub fn overlay(self) -> overlay::Element<'a, Message, Renderer> {
|
||||||
|
overlay::Element::new(Point::ORIGIN, Box::new(self))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, Message, Renderer> Default for Group<'a, Message, Renderer>
|
||||||
|
where
|
||||||
|
Renderer: 'a + crate::Renderer,
|
||||||
|
Message: 'a,
|
||||||
|
{
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::with_children(Vec::new())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, Message, Renderer> Overlay<Message, Renderer>
|
||||||
|
for Group<'a, Message, Renderer>
|
||||||
|
where
|
||||||
|
Renderer: crate::Renderer,
|
||||||
|
{
|
||||||
|
fn layout(
|
||||||
|
&self,
|
||||||
|
renderer: &Renderer,
|
||||||
|
bounds: Size,
|
||||||
|
position: Point,
|
||||||
|
) -> layout::Node {
|
||||||
|
let translation = position - Point::ORIGIN;
|
||||||
|
|
||||||
|
layout::Node::with_children(
|
||||||
|
bounds,
|
||||||
|
self.children
|
||||||
|
.iter()
|
||||||
|
.map(|child| child.layout(renderer, bounds, translation))
|
||||||
|
.collect(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_event(
|
||||||
|
&mut self,
|
||||||
|
event: Event,
|
||||||
|
layout: Layout<'_>,
|
||||||
|
cursor_position: Point,
|
||||||
|
renderer: &Renderer,
|
||||||
|
clipboard: &mut dyn Clipboard,
|
||||||
|
shell: &mut Shell<'_, Message>,
|
||||||
|
) -> event::Status {
|
||||||
|
self.children
|
||||||
|
.iter_mut()
|
||||||
|
.zip(layout.children())
|
||||||
|
.map(|(child, layout)| {
|
||||||
|
child.on_event(
|
||||||
|
event.clone(),
|
||||||
|
layout,
|
||||||
|
cursor_position,
|
||||||
|
renderer,
|
||||||
|
clipboard,
|
||||||
|
shell,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.fold(event::Status::Ignored, event::Status::merge)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw(
|
||||||
|
&self,
|
||||||
|
renderer: &mut Renderer,
|
||||||
|
theme: &<Renderer as crate::Renderer>::Theme,
|
||||||
|
style: &renderer::Style,
|
||||||
|
layout: Layout<'_>,
|
||||||
|
cursor_position: Point,
|
||||||
|
) {
|
||||||
|
for (child, layout) in self.children.iter().zip(layout.children()) {
|
||||||
|
child.draw(renderer, theme, style, layout, cursor_position);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn mouse_interaction(
|
||||||
|
&self,
|
||||||
|
layout: Layout<'_>,
|
||||||
|
cursor_position: Point,
|
||||||
|
viewport: &Rectangle,
|
||||||
|
renderer: &Renderer,
|
||||||
|
) -> mouse::Interaction {
|
||||||
|
self.children
|
||||||
|
.iter()
|
||||||
|
.zip(layout.children())
|
||||||
|
.map(|(child, layout)| {
|
||||||
|
child.mouse_interaction(
|
||||||
|
layout,
|
||||||
|
cursor_position,
|
||||||
|
viewport,
|
||||||
|
renderer,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.max()
|
||||||
|
.unwrap_or_default()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn operate(
|
||||||
|
&mut self,
|
||||||
|
layout: Layout<'_>,
|
||||||
|
renderer: &Renderer,
|
||||||
|
operation: &mut dyn widget::Operation<Message>,
|
||||||
|
) {
|
||||||
|
operation.container(None, &mut |operation| {
|
||||||
|
self.children.iter_mut().zip(layout.children()).for_each(
|
||||||
|
|(child, layout)| {
|
||||||
|
child.operate(layout, renderer, operation);
|
||||||
|
},
|
||||||
|
)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_over(&self, layout: Layout<'_>, cursor_position: Point) -> bool {
|
||||||
|
self.children
|
||||||
|
.iter()
|
||||||
|
.zip(layout.children())
|
||||||
|
.any(|(child, layout)| child.is_over(layout, cursor_position))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, Message, Renderer> From<Group<'a, Message, Renderer>>
|
||||||
|
for overlay::Element<'a, Message, Renderer>
|
||||||
|
where
|
||||||
|
Renderer: 'a + crate::Renderer,
|
||||||
|
Message: 'a,
|
||||||
|
{
|
||||||
|
fn from(group: Group<'a, Message, Renderer>) -> Self {
|
||||||
|
group.overlay()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -281,10 +281,7 @@ where
|
||||||
|
|
||||||
renderer.fill_quad(
|
renderer.fill_quad(
|
||||||
renderer::Quad {
|
renderer::Quad {
|
||||||
bounds: Rectangle {
|
bounds,
|
||||||
width: bounds.width - 1.0,
|
|
||||||
..bounds
|
|
||||||
},
|
|
||||||
border_color: appearance.border_color,
|
border_color: appearance.border_color,
|
||||||
border_width: appearance.border_width,
|
border_width: appearance.border_width,
|
||||||
border_radius: appearance.border_radius.into(),
|
border_radius: appearance.border_radius.into(),
|
||||||
|
|
|
||||||
|
|
@ -16,9 +16,9 @@ pub trait Renderer: Sized {
|
||||||
///
|
///
|
||||||
/// You should override this if you need to perform any operations before or
|
/// You should override this if you need to perform any operations before or
|
||||||
/// after layouting. For instance, trimming the measurements cache.
|
/// after layouting. For instance, trimming the measurements cache.
|
||||||
fn layout<'a, Message>(
|
fn layout<Message>(
|
||||||
&mut self,
|
&mut self,
|
||||||
element: &Element<'a, Message, Self>,
|
element: &Element<'_, Message, Self>,
|
||||||
limits: &layout::Limits,
|
limits: &layout::Limits,
|
||||||
) -> layout::Node {
|
) -> layout::Node {
|
||||||
element.as_widget().layout(self, limits)
|
element.as_widget().layout(self, limits)
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,11 @@ impl<'a, Message> Shell<'a, Message> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns true if the [`Shell`] contains no published messages
|
||||||
|
pub fn is_empty(&self) -> bool {
|
||||||
|
self.messages.is_empty()
|
||||||
|
}
|
||||||
|
|
||||||
/// Publish the given `Message` for an application to process it.
|
/// Publish the given `Message` for an application to process it.
|
||||||
pub fn publish(&mut self, message: Message) {
|
pub fn publish(&mut self, message: Message) {
|
||||||
self.messages.push(message);
|
self.messages.push(message);
|
||||||
|
|
|
||||||
|
|
@ -71,7 +71,7 @@ pub enum Data {
|
||||||
impl std::fmt::Debug for Data {
|
impl std::fmt::Debug for Data {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
Data::Path(path) => write!(f, "Path({:?})", path),
|
Data::Path(path) => write!(f, "Path({path:?})"),
|
||||||
Data::Bytes(_) => write!(f, "Bytes(...)"),
|
Data::Bytes(_) => write!(f, "Bytes(...)"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,9 @@ use crate::mouse;
|
||||||
use crate::renderer;
|
use crate::renderer;
|
||||||
use crate::widget;
|
use crate::widget;
|
||||||
use crate::window;
|
use crate::window;
|
||||||
use crate::{Clipboard, Element, Layout, Point, Rectangle, Shell, Size};
|
use crate::{
|
||||||
|
Clipboard, Element, Layout, Point, Rectangle, Shell, Size, Vector,
|
||||||
|
};
|
||||||
|
|
||||||
/// A set of interactive graphical elements with a specific [`Layout`].
|
/// A set of interactive graphical elements with a specific [`Layout`].
|
||||||
///
|
///
|
||||||
|
|
@ -203,7 +205,7 @@ where
|
||||||
let bounds = self.bounds;
|
let bounds = self.bounds;
|
||||||
|
|
||||||
let mut overlay = manual_overlay.as_mut().unwrap();
|
let mut overlay = manual_overlay.as_mut().unwrap();
|
||||||
let mut layout = overlay.layout(renderer, bounds);
|
let mut layout = overlay.layout(renderer, bounds, Vector::ZERO);
|
||||||
let mut event_statuses = Vec::new();
|
let mut event_statuses = Vec::new();
|
||||||
|
|
||||||
for event in events.iter().cloned() {
|
for event in events.iter().cloned() {
|
||||||
|
|
@ -252,7 +254,7 @@ where
|
||||||
overlay = manual_overlay.as_mut().unwrap();
|
overlay = manual_overlay.as_mut().unwrap();
|
||||||
|
|
||||||
shell.revalidate_layout(|| {
|
shell.revalidate_layout(|| {
|
||||||
layout = overlay.layout(renderer, bounds);
|
layout = overlay.layout(renderer, bounds, Vector::ZERO);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -261,12 +263,16 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let base_cursor = if layout.bounds().contains(cursor_position) {
|
let base_cursor = manual_overlay
|
||||||
// TODO: Type-safe cursor availability
|
.as_ref()
|
||||||
Point::new(-1.0, -1.0)
|
.filter(|overlay| {
|
||||||
} else {
|
overlay.is_over(Layout::new(&layout), cursor_position)
|
||||||
cursor_position
|
})
|
||||||
};
|
.map(|_| {
|
||||||
|
// TODO: Type-safe cursor availability
|
||||||
|
Point::new(-1.0, -1.0)
|
||||||
|
})
|
||||||
|
.unwrap_or(cursor_position);
|
||||||
|
|
||||||
self.overlay = Some(layout);
|
self.overlay = Some(layout);
|
||||||
|
|
||||||
|
|
@ -430,10 +436,9 @@ where
|
||||||
.as_widget_mut()
|
.as_widget_mut()
|
||||||
.overlay(&mut self.state, Layout::new(&self.base), renderer)
|
.overlay(&mut self.state, Layout::new(&self.base), renderer)
|
||||||
{
|
{
|
||||||
let overlay_layout = self
|
let overlay_layout = self.overlay.take().unwrap_or_else(|| {
|
||||||
.overlay
|
overlay.layout(renderer, self.bounds, Vector::ZERO)
|
||||||
.take()
|
});
|
||||||
.unwrap_or_else(|| overlay.layout(renderer, self.bounds));
|
|
||||||
|
|
||||||
let new_cursor_position =
|
let new_cursor_position =
|
||||||
if overlay_layout.bounds().contains(cursor_position) {
|
if overlay_layout.bounds().contains(cursor_position) {
|
||||||
|
|
@ -504,7 +509,8 @@ where
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
if overlay_bounds.contains(cursor_position) {
|
if overlay.is_over(Layout::new(layout), cursor_position)
|
||||||
|
{
|
||||||
overlay_interaction
|
overlay_interaction
|
||||||
} else {
|
} else {
|
||||||
base_interaction
|
base_interaction
|
||||||
|
|
@ -533,7 +539,8 @@ where
|
||||||
renderer,
|
renderer,
|
||||||
) {
|
) {
|
||||||
if self.overlay.is_none() {
|
if self.overlay.is_none() {
|
||||||
self.overlay = Some(overlay.layout(renderer, self.bounds));
|
self.overlay =
|
||||||
|
Some(overlay.layout(renderer, self.bounds, Vector::ZERO));
|
||||||
}
|
}
|
||||||
|
|
||||||
overlay.operate(
|
overlay.operate(
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
use crate::widget::operation::{self, Focusable, Operation, Scrollable};
|
use crate::widget::operation::{
|
||||||
|
self, Focusable, Operation, Scrollable, TextInput,
|
||||||
|
};
|
||||||
use crate::widget::Id;
|
use crate::widget::Id;
|
||||||
|
|
||||||
use iced_futures::MaybeSend;
|
use iced_futures::MaybeSend;
|
||||||
|
|
@ -86,6 +88,14 @@ where
|
||||||
self.operation.focusable(state, id);
|
self.operation.focusable(state, id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn text_input(
|
||||||
|
&mut self,
|
||||||
|
state: &mut dyn TextInput,
|
||||||
|
id: Option<&Id>,
|
||||||
|
) {
|
||||||
|
self.operation.text_input(state, id);
|
||||||
|
}
|
||||||
|
|
||||||
fn custom(&mut self, state: &mut dyn Any, id: Option<&Id>) {
|
fn custom(&mut self, state: &mut dyn Any, id: Option<&Id>) {
|
||||||
self.operation.custom(state, id);
|
self.operation.custom(state, id);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -111,6 +111,45 @@ where
|
||||||
layout::Node::new(final_size)
|
layout::Node::new(final_size)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Draws an [`Image`]
|
||||||
|
pub fn draw<Renderer, Handle>(
|
||||||
|
renderer: &mut Renderer,
|
||||||
|
layout: Layout<'_>,
|
||||||
|
handle: &Handle,
|
||||||
|
content_fit: ContentFit,
|
||||||
|
) where
|
||||||
|
Renderer: image::Renderer<Handle = Handle>,
|
||||||
|
Handle: Clone + Hash,
|
||||||
|
{
|
||||||
|
let Size { width, height } = renderer.dimensions(handle);
|
||||||
|
let image_size = Size::new(width as f32, height as f32);
|
||||||
|
|
||||||
|
let bounds = layout.bounds();
|
||||||
|
let adjusted_fit = content_fit.fit(image_size, bounds.size());
|
||||||
|
|
||||||
|
let render = |renderer: &mut Renderer| {
|
||||||
|
let offset = Vector::new(
|
||||||
|
(bounds.width - adjusted_fit.width).max(0.0) / 2.0,
|
||||||
|
(bounds.height - adjusted_fit.height).max(0.0) / 2.0,
|
||||||
|
);
|
||||||
|
|
||||||
|
let drawing_bounds = Rectangle {
|
||||||
|
width: adjusted_fit.width,
|
||||||
|
height: adjusted_fit.height,
|
||||||
|
..bounds
|
||||||
|
};
|
||||||
|
|
||||||
|
renderer.draw(handle.clone(), drawing_bounds + offset)
|
||||||
|
};
|
||||||
|
|
||||||
|
if adjusted_fit.width > bounds.width || adjusted_fit.height > bounds.height
|
||||||
|
{
|
||||||
|
renderer.with_layer(bounds, render);
|
||||||
|
} else {
|
||||||
|
render(renderer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<Message, Renderer, Handle> Widget<Message, Renderer> for Image<Handle>
|
impl<Message, Renderer, Handle> Widget<Message, Renderer> for Image<Handle>
|
||||||
where
|
where
|
||||||
Renderer: image::Renderer<Handle = Handle>,
|
Renderer: image::Renderer<Handle = Handle>,
|
||||||
|
|
@ -149,34 +188,7 @@ where
|
||||||
_cursor_position: Point,
|
_cursor_position: Point,
|
||||||
_viewport: &Rectangle,
|
_viewport: &Rectangle,
|
||||||
) {
|
) {
|
||||||
let Size { width, height } = renderer.dimensions(&self.handle);
|
draw(renderer, layout, &self.handle, self.content_fit)
|
||||||
let image_size = Size::new(width as f32, height as f32);
|
|
||||||
|
|
||||||
let bounds = layout.bounds();
|
|
||||||
let adjusted_fit = self.content_fit.fit(image_size, bounds.size());
|
|
||||||
|
|
||||||
let render = |renderer: &mut Renderer| {
|
|
||||||
let offset = Vector::new(
|
|
||||||
(bounds.width - adjusted_fit.width).max(0.0) / 2.0,
|
|
||||||
(bounds.height - adjusted_fit.height).max(0.0) / 2.0,
|
|
||||||
);
|
|
||||||
|
|
||||||
let drawing_bounds = Rectangle {
|
|
||||||
width: adjusted_fit.width,
|
|
||||||
height: adjusted_fit.height,
|
|
||||||
..bounds
|
|
||||||
};
|
|
||||||
|
|
||||||
renderer.draw(self.handle.clone(), drawing_bounds + offset)
|
|
||||||
};
|
|
||||||
|
|
||||||
if adjusted_fit.width > bounds.width
|
|
||||||
|| adjusted_fit.height > bounds.height
|
|
||||||
{
|
|
||||||
renderer.with_layer(bounds, render);
|
|
||||||
} else {
|
|
||||||
render(renderer)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -62,7 +62,7 @@ where
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
Self::None => write!(f, "Outcome::None"),
|
Self::None => write!(f, "Outcome::None"),
|
||||||
Self::Some(output) => write!(f, "Outcome::Some({:?})", output),
|
Self::Some(output) => write!(f, "Outcome::Some({output:?})"),
|
||||||
Self::Chain(_) => write!(f, "Outcome::Chain(...)"),
|
Self::Chain(_) => write!(f, "Outcome::Chain(...)"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,7 @@ pub use iced_style::pane_grid::{Line, StyleSheet};
|
||||||
use crate::event::{self, Event};
|
use crate::event::{self, Event};
|
||||||
use crate::layout;
|
use crate::layout;
|
||||||
use crate::mouse;
|
use crate::mouse;
|
||||||
use crate::overlay;
|
use crate::overlay::{self, Group};
|
||||||
use crate::renderer;
|
use crate::renderer;
|
||||||
use crate::touch;
|
use crate::touch;
|
||||||
use crate::widget;
|
use crate::widget;
|
||||||
|
|
@ -450,14 +450,17 @@ where
|
||||||
layout: Layout<'_>,
|
layout: Layout<'_>,
|
||||||
renderer: &Renderer,
|
renderer: &Renderer,
|
||||||
) -> Option<overlay::Element<'_, Message, Renderer>> {
|
) -> Option<overlay::Element<'_, Message, Renderer>> {
|
||||||
self.contents
|
let children = self
|
||||||
|
.contents
|
||||||
.iter_mut()
|
.iter_mut()
|
||||||
.zip(&mut tree.children)
|
.zip(&mut tree.children)
|
||||||
.zip(layout.children())
|
.zip(layout.children())
|
||||||
.filter_map(|(((_, pane), tree), layout)| {
|
.filter_map(|(((_, content), state), layout)| {
|
||||||
pane.overlay(tree, layout, renderer)
|
content.overlay(state, layout, renderer)
|
||||||
})
|
})
|
||||||
.next()
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
(!children.is_empty()).then(|| Group::with_children(children).overlay())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -20,60 +20,6 @@ use std::borrow::Cow;
|
||||||
|
|
||||||
pub use iced_style::pick_list::{Appearance, StyleSheet};
|
pub use iced_style::pick_list::{Appearance, StyleSheet};
|
||||||
|
|
||||||
/// The handle to the right side of the [`PickList`].
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
||||||
pub enum Handle<Renderer>
|
|
||||||
where
|
|
||||||
Renderer: text::Renderer,
|
|
||||||
{
|
|
||||||
/// Displays an arrow icon (▼).
|
|
||||||
///
|
|
||||||
/// This is the default.
|
|
||||||
Arrow {
|
|
||||||
/// Font size of the content.
|
|
||||||
size: Option<u16>,
|
|
||||||
},
|
|
||||||
/// A custom handle.
|
|
||||||
Custom {
|
|
||||||
/// Font that will be used to display the `text`,
|
|
||||||
font: Renderer::Font,
|
|
||||||
/// Text that will be shown.
|
|
||||||
text: String,
|
|
||||||
/// Font size of the content.
|
|
||||||
size: Option<u16>,
|
|
||||||
},
|
|
||||||
/// No handle will be shown.
|
|
||||||
None,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<Renderer> Default for Handle<Renderer>
|
|
||||||
where
|
|
||||||
Renderer: text::Renderer,
|
|
||||||
{
|
|
||||||
fn default() -> Self {
|
|
||||||
Self::Arrow { size: None }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<Renderer> Handle<Renderer>
|
|
||||||
where
|
|
||||||
Renderer: text::Renderer,
|
|
||||||
{
|
|
||||||
fn content(&self) -> Option<(Renderer::Font, String, Option<u16>)> {
|
|
||||||
match self {
|
|
||||||
Self::Arrow { size } => Some((
|
|
||||||
Renderer::ICON_FONT,
|
|
||||||
Renderer::ARROW_DOWN_ICON.to_string(),
|
|
||||||
*size,
|
|
||||||
)),
|
|
||||||
Self::Custom { font, text, size } => {
|
|
||||||
Some((font.clone(), text.clone(), *size))
|
|
||||||
}
|
|
||||||
Self::None => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A widget for selecting a single value from a list of options.
|
/// A widget for selecting a single value from a list of options.
|
||||||
#[allow(missing_debug_implementations)]
|
#[allow(missing_debug_implementations)]
|
||||||
pub struct PickList<'a, T, Message, Renderer>
|
pub struct PickList<'a, T, Message, Renderer>
|
||||||
|
|
@ -90,7 +36,7 @@ where
|
||||||
padding: Padding,
|
padding: Padding,
|
||||||
text_size: Option<u16>,
|
text_size: Option<u16>,
|
||||||
font: Renderer::Font,
|
font: Renderer::Font,
|
||||||
handle: Handle<Renderer>,
|
handle: Handle<Renderer::Font>,
|
||||||
style: <Renderer::Theme as StyleSheet>::Style,
|
style: <Renderer::Theme as StyleSheet>::Style,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -161,7 +107,7 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the [`Handle`] of the [`PickList`].
|
/// Sets the [`Handle`] of the [`PickList`].
|
||||||
pub fn handle(mut self, handle: Handle<Renderer>) -> Self {
|
pub fn handle(mut self, handle: Handle<Renderer::Font>) -> Self {
|
||||||
self.handle = handle;
|
self.handle = handle;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
@ -258,7 +204,7 @@ where
|
||||||
|
|
||||||
fn draw(
|
fn draw(
|
||||||
&self,
|
&self,
|
||||||
_tree: &Tree,
|
tree: &Tree,
|
||||||
renderer: &mut Renderer,
|
renderer: &mut Renderer,
|
||||||
theme: &Renderer::Theme,
|
theme: &Renderer::Theme,
|
||||||
_style: &renderer::Style,
|
_style: &renderer::Style,
|
||||||
|
|
@ -278,6 +224,7 @@ where
|
||||||
self.selected.as_ref(),
|
self.selected.as_ref(),
|
||||||
&self.handle,
|
&self.handle,
|
||||||
&self.style,
|
&self.style,
|
||||||
|
|| tree.state.downcast_ref::<State<T>>(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -349,6 +296,46 @@ impl<T> Default for State<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The handle to the right side of the [`PickList`].
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub enum Handle<Font> {
|
||||||
|
/// Displays an arrow icon (▼).
|
||||||
|
///
|
||||||
|
/// This is the default.
|
||||||
|
Arrow {
|
||||||
|
/// Font size of the content.
|
||||||
|
size: Option<u16>,
|
||||||
|
},
|
||||||
|
/// A custom static handle.
|
||||||
|
Static(Icon<Font>),
|
||||||
|
/// A custom dynamic handle.
|
||||||
|
Dynamic {
|
||||||
|
/// The [`Icon`] used when [`PickList`] is closed.
|
||||||
|
closed: Icon<Font>,
|
||||||
|
/// The [`Icon`] used when [`PickList`] is open.
|
||||||
|
open: Icon<Font>,
|
||||||
|
},
|
||||||
|
/// No handle will be shown.
|
||||||
|
None,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Font> Default for Handle<Font> {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::Arrow { size: None }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The icon of a [`Handle`].
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub struct Icon<Font> {
|
||||||
|
/// Font that will be used to display the `code_point`,
|
||||||
|
pub font: Font,
|
||||||
|
/// The unicode code point that will be used as the icon.
|
||||||
|
pub code_point: char,
|
||||||
|
/// Font size of the content.
|
||||||
|
pub size: Option<u16>,
|
||||||
|
}
|
||||||
|
|
||||||
/// Computes the layout of a [`PickList`].
|
/// Computes the layout of a [`PickList`].
|
||||||
pub fn layout<Renderer, T>(
|
pub fn layout<Renderer, T>(
|
||||||
renderer: &Renderer,
|
renderer: &Renderer,
|
||||||
|
|
@ -568,7 +555,7 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Draws a [`PickList`].
|
/// Draws a [`PickList`].
|
||||||
pub fn draw<T, Renderer>(
|
pub fn draw<'a, T, Renderer>(
|
||||||
renderer: &mut Renderer,
|
renderer: &mut Renderer,
|
||||||
theme: &Renderer::Theme,
|
theme: &Renderer::Theme,
|
||||||
layout: Layout<'_>,
|
layout: Layout<'_>,
|
||||||
|
|
@ -578,12 +565,13 @@ pub fn draw<T, Renderer>(
|
||||||
font: &Renderer::Font,
|
font: &Renderer::Font,
|
||||||
placeholder: Option<&str>,
|
placeholder: Option<&str>,
|
||||||
selected: Option<&T>,
|
selected: Option<&T>,
|
||||||
handle: &Handle<Renderer>,
|
handle: &Handle<Renderer::Font>,
|
||||||
style: &<Renderer::Theme as StyleSheet>::Style,
|
style: &<Renderer::Theme as StyleSheet>::Style,
|
||||||
|
state: impl FnOnce() -> &'a State<T>,
|
||||||
) where
|
) where
|
||||||
Renderer: text::Renderer,
|
Renderer: text::Renderer,
|
||||||
Renderer::Theme: StyleSheet,
|
Renderer::Theme: StyleSheet,
|
||||||
T: ToString,
|
T: ToString + 'a,
|
||||||
{
|
{
|
||||||
let bounds = layout.bounds();
|
let bounds = layout.bounds();
|
||||||
let is_mouse_over = bounds.contains(cursor_position);
|
let is_mouse_over = bounds.contains(cursor_position);
|
||||||
|
|
@ -605,11 +593,30 @@ pub fn draw<T, Renderer>(
|
||||||
style.background,
|
style.background,
|
||||||
);
|
);
|
||||||
|
|
||||||
if let Some((font, text, size)) = handle.content() {
|
let handle = match handle {
|
||||||
|
Handle::Arrow { size } => {
|
||||||
|
Some((Renderer::ICON_FONT, Renderer::ARROW_DOWN_ICON, *size))
|
||||||
|
}
|
||||||
|
Handle::Static(Icon {
|
||||||
|
font,
|
||||||
|
code_point,
|
||||||
|
size,
|
||||||
|
}) => Some((font.clone(), *code_point, *size)),
|
||||||
|
Handle::Dynamic { open, closed } => {
|
||||||
|
if state().is_open {
|
||||||
|
Some((open.font.clone(), open.code_point, open.size))
|
||||||
|
} else {
|
||||||
|
Some((closed.font.clone(), closed.code_point, closed.size))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Handle::None => None,
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some((font, code_point, size)) = handle {
|
||||||
let size = f32::from(size.unwrap_or_else(|| renderer.default_size()));
|
let size = f32::from(size.unwrap_or_else(|| renderer.default_size()));
|
||||||
|
|
||||||
renderer.fill_text(Text {
|
renderer.fill_text(Text {
|
||||||
content: &text,
|
content: &code_point.to_string(),
|
||||||
size,
|
size,
|
||||||
font,
|
font,
|
||||||
color: style.handle_color,
|
color: style.handle_color,
|
||||||
|
|
|
||||||
|
|
@ -389,8 +389,8 @@ where
|
||||||
let padding = padding.fit(Size::ZERO, limits.max());
|
let padding = padding.fit(Size::ZERO, limits.max());
|
||||||
|
|
||||||
let limits = limits
|
let limits = limits
|
||||||
.pad(padding)
|
|
||||||
.width(width)
|
.width(width)
|
||||||
|
.pad(padding)
|
||||||
.height(Length::Units(text_size));
|
.height(Length::Units(text_size));
|
||||||
|
|
||||||
let mut text = layout::Node::new(limits.resolve(Size::ZERO));
|
let mut text = layout::Node::new(limits.resolve(Size::ZERO));
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::window;
|
use crate::window::{Mode, UserAttention};
|
||||||
|
|
||||||
use iced_futures::MaybeSend;
|
use iced_futures::MaybeSend;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
@ -38,20 +38,21 @@ pub enum Action<T> {
|
||||||
/// The new logical y location of the window
|
/// The new logical y location of the window
|
||||||
y: i32,
|
y: i32,
|
||||||
},
|
},
|
||||||
/// Set the [`Mode`] of the window.
|
/// Change the [`Mode`] of the window.
|
||||||
SetMode(window::Mode),
|
ChangeMode(Mode),
|
||||||
/// Fetch the current [`Mode`] of the window.
|
/// Fetch the current [`Mode`] of the window.
|
||||||
FetchMode(Box<dyn FnOnce(window::Mode) -> T + 'static>),
|
FetchMode(Box<dyn FnOnce(Mode) -> T + 'static>),
|
||||||
/// Sets the window to maximized or back
|
/// Toggle the window to maximized or back
|
||||||
ToggleMaximize,
|
ToggleMaximize,
|
||||||
/// Toggles whether window has decorations
|
/// Toggle whether window has decorations.
|
||||||
|
///
|
||||||
/// ## Platform-specific
|
/// ## Platform-specific
|
||||||
/// - **X11:** Not implemented.
|
/// - **X11:** Not implemented.
|
||||||
/// - **Web:** Unsupported.
|
/// - **Web:** Unsupported.
|
||||||
ToggleDecorations,
|
ToggleDecorations,
|
||||||
/// Requests user attention to the window, this has no effect if the application
|
/// Request user attention to the window, this has no effect if the application
|
||||||
/// is already focused. How requesting for user attention manifests is platform dependent,
|
/// is already focused. How requesting for user attention manifests is platform dependent,
|
||||||
/// see [`UserAttentionType`] for details.
|
/// see [`UserAttention`] for details.
|
||||||
///
|
///
|
||||||
/// Providing `None` will unset the request for user attention. Unsetting the request for
|
/// Providing `None` will unset the request for user attention. Unsetting the request for
|
||||||
/// user attention might not be done automatically by the WM when the window receives input.
|
/// user attention might not be done automatically by the WM when the window receives input.
|
||||||
|
|
@ -62,8 +63,8 @@ pub enum Action<T> {
|
||||||
/// - **macOS:** `None` has no effect.
|
/// - **macOS:** `None` has no effect.
|
||||||
/// - **X11:** Requests for user attention must be manually cleared.
|
/// - **X11:** Requests for user attention must be manually cleared.
|
||||||
/// - **Wayland:** Requires `xdg_activation_v1` protocol, `None` has no effect.
|
/// - **Wayland:** Requires `xdg_activation_v1` protocol, `None` has no effect.
|
||||||
RequestUserAttention(Option<window::UserAttention>),
|
RequestUserAttention(Option<UserAttention>),
|
||||||
/// Brings the window to the front and sets input focus. Has no effect if the window is
|
/// Bring the window to the front and sets input focus. Has no effect if the window is
|
||||||
/// already in focus, minimized, or not visible.
|
/// already in focus, minimized, or not visible.
|
||||||
///
|
///
|
||||||
/// This method steals input focus from other applications. Do not use this method unless
|
/// This method steals input focus from other applications. Do not use this method unless
|
||||||
|
|
@ -93,7 +94,7 @@ impl<T> Action<T> {
|
||||||
Self::Maximize(bool) => Action::Maximize(bool),
|
Self::Maximize(bool) => Action::Maximize(bool),
|
||||||
Self::Minimize(bool) => Action::Minimize(bool),
|
Self::Minimize(bool) => Action::Minimize(bool),
|
||||||
Self::Move { x, y } => Action::Move { x, y },
|
Self::Move { x, y } => Action::Move { x, y },
|
||||||
Self::SetMode(mode) => Action::SetMode(mode),
|
Self::ChangeMode(mode) => Action::ChangeMode(mode),
|
||||||
Self::FetchMode(o) => Action::FetchMode(Box::new(move |s| f(o(s)))),
|
Self::FetchMode(o) => Action::FetchMode(Box::new(move |s| f(o(s)))),
|
||||||
Self::ToggleMaximize => Action::ToggleMaximize,
|
Self::ToggleMaximize => Action::ToggleMaximize,
|
||||||
Self::ToggleDecorations => Action::ToggleDecorations,
|
Self::ToggleDecorations => Action::ToggleDecorations,
|
||||||
|
|
@ -115,15 +116,14 @@ impl<T> fmt::Debug for Action<T> {
|
||||||
}
|
}
|
||||||
Self::Resize { width, height } => write!(
|
Self::Resize { width, height } => write!(
|
||||||
f,
|
f,
|
||||||
"Action::Resize {{ widget: {}, height: {} }}",
|
"Action::Resize {{ widget: {width}, height: {height} }}"
|
||||||
width, height
|
|
||||||
),
|
),
|
||||||
Self::Maximize(value) => write!(f, "Action::Maximize({})", value),
|
Self::Maximize(value) => write!(f, "Action::Maximize({value})"),
|
||||||
Self::Minimize(value) => write!(f, "Action::Minimize({}", value),
|
Self::Minimize(value) => write!(f, "Action::Minimize({value}"),
|
||||||
Self::Move { x, y } => {
|
Self::Move { x, y } => {
|
||||||
write!(f, "Action::Move {{ x: {}, y: {} }}", x, y)
|
write!(f, "Action::Move {{ x: {x}, y: {y} }}")
|
||||||
}
|
}
|
||||||
Self::SetMode(mode) => write!(f, "Action::SetMode({:?})", mode),
|
Self::ChangeMode(mode) => write!(f, "Action::SetMode({mode:?})"),
|
||||||
Self::FetchMode(_) => write!(f, "Action::FetchMode"),
|
Self::FetchMode(_) => write!(f, "Action::FetchMode"),
|
||||||
Self::ToggleMaximize => write!(f, "Action::ToggleMaximize"),
|
Self::ToggleMaximize => write!(f, "Action::ToggleMaximize"),
|
||||||
Self::ToggleDecorations => write!(f, "Action::ToggleDecorations"),
|
Self::ToggleDecorations => write!(f, "Action::ToggleDecorations"),
|
||||||
|
|
|
||||||
|
|
@ -80,7 +80,9 @@ pub mod pane_grid {
|
||||||
|
|
||||||
pub mod pick_list {
|
pub mod pick_list {
|
||||||
//! Display a dropdown list of selectable values.
|
//! Display a dropdown list of selectable values.
|
||||||
pub use iced_native::widget::pick_list::{Appearance, Handle, StyleSheet};
|
pub use iced_native::widget::pick_list::{
|
||||||
|
Appearance, Handle, Icon, StyleSheet,
|
||||||
|
};
|
||||||
|
|
||||||
/// A widget allowing the selection of a single value from a list of options.
|
/// A widget allowing the selection of a single value from a list of options.
|
||||||
pub type PickList<'a, T, Message, Renderer = crate::Renderer> =
|
pub type PickList<'a, T, Message, Renderer = crate::Renderer> =
|
||||||
|
|
|
||||||
|
|
@ -83,7 +83,7 @@ fn backend_from_env() -> Option<wgpu::Backends> {
|
||||||
"gl" => wgpu::Backends::GL,
|
"gl" => wgpu::Backends::GL,
|
||||||
"webgpu" => wgpu::Backends::BROWSER_WEBGPU,
|
"webgpu" => wgpu::Backends::BROWSER_WEBGPU,
|
||||||
"primary" => wgpu::Backends::PRIMARY,
|
"primary" => wgpu::Backends::PRIMARY,
|
||||||
other => panic!("Unknown backend: {}", other),
|
other => panic!("Unknown backend: {other}"),
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -145,11 +145,15 @@ where
|
||||||
#[cfg(target_arch = "wasm32")]
|
#[cfg(target_arch = "wasm32")]
|
||||||
let target = settings.window.platform_specific.target.clone();
|
let target = settings.window.platform_specific.target.clone();
|
||||||
|
|
||||||
let builder = settings.window.into_builder(
|
let should_be_visible = settings.window.visible;
|
||||||
&application.title(),
|
let builder = settings
|
||||||
event_loop.primary_monitor(),
|
.window
|
||||||
settings.id,
|
.into_builder(
|
||||||
);
|
&application.title(),
|
||||||
|
event_loop.primary_monitor(),
|
||||||
|
settings.id,
|
||||||
|
)
|
||||||
|
.with_visible(false);
|
||||||
|
|
||||||
log::info!("Window builder: {:#?}", builder);
|
log::info!("Window builder: {:#?}", builder);
|
||||||
|
|
||||||
|
|
@ -200,6 +204,7 @@ where
|
||||||
control_sender,
|
control_sender,
|
||||||
init_command,
|
init_command,
|
||||||
window,
|
window,
|
||||||
|
should_be_visible,
|
||||||
settings.exit_on_close_request,
|
settings.exit_on_close_request,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -266,6 +271,7 @@ async fn run_instance<A, E, C>(
|
||||||
mut control_sender: mpsc::UnboundedSender<winit::event_loop::ControlFlow>,
|
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,
|
||||||
|
should_be_visible: bool,
|
||||||
exit_on_close_request: bool,
|
exit_on_close_request: bool,
|
||||||
) where
|
) where
|
||||||
A: Application + 'static,
|
A: Application + 'static,
|
||||||
|
|
@ -293,6 +299,10 @@ async fn run_instance<A, E, C>(
|
||||||
physical_size.height,
|
physical_size.height,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if should_be_visible {
|
||||||
|
window.set_visible(true);
|
||||||
|
}
|
||||||
|
|
||||||
run_command(
|
run_command(
|
||||||
&application,
|
&application,
|
||||||
&mut cache,
|
&mut cache,
|
||||||
|
|
@ -534,7 +544,7 @@ async fn run_instance<A, E, C>(
|
||||||
Err(error) => match error {
|
Err(error) => match error {
|
||||||
// This is an unrecoverable error.
|
// This is an unrecoverable error.
|
||||||
compositor::SurfaceError::OutOfMemory => {
|
compositor::SurfaceError::OutOfMemory => {
|
||||||
panic!("{:?}", error);
|
panic!("{error:?}");
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
debug.render_finished();
|
debug.render_finished();
|
||||||
|
|
@ -754,7 +764,7 @@ pub fn run_command<A, E>(
|
||||||
y,
|
y,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
window::Action::SetMode(mode) => {
|
window::Action::ChangeMode(mode) => {
|
||||||
window.set_visible(conversion::visible(mode));
|
window.set_visible(conversion::visible(mode));
|
||||||
window.set_fullscreen(conversion::fullscreen(
|
window.set_fullscreen(conversion::fullscreen(
|
||||||
window.primary_monitor(),
|
window.primary_monitor(),
|
||||||
|
|
|
||||||
|
|
@ -132,10 +132,9 @@ impl fmt::Display for Error {
|
||||||
Error::InvalidData { byte_count } => {
|
Error::InvalidData { byte_count } => {
|
||||||
write!(
|
write!(
|
||||||
f,
|
f,
|
||||||
"The provided RGBA data (with length {:?}) isn't divisble by \
|
"The provided RGBA data (with length {byte_count:?}) isn't divisble by \
|
||||||
4. Therefore, it cannot be safely interpreted as 32bpp RGBA \
|
4. Therefore, it cannot be safely interpreted as 32bpp RGBA \
|
||||||
pixels.",
|
pixels."
|
||||||
byte_count,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
Error::DimensionsMismatch {
|
Error::DimensionsMismatch {
|
||||||
|
|
@ -145,20 +144,18 @@ impl fmt::Display for Error {
|
||||||
} => {
|
} => {
|
||||||
write!(
|
write!(
|
||||||
f,
|
f,
|
||||||
"The number of RGBA pixels ({:?}) does not match the provided \
|
"The number of RGBA pixels ({pixel_count:?}) does not match the provided \
|
||||||
dimensions ({:?}x{:?}).",
|
dimensions ({width:?}x{height:?})."
|
||||||
pixel_count, width, height,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
Error::OsError(e) => write!(
|
Error::OsError(e) => write!(
|
||||||
f,
|
f,
|
||||||
"The underlying OS failed to create the window \
|
"The underlying OS failed to create the window \
|
||||||
icon: {:?}",
|
icon: {e:?}"
|
||||||
e
|
|
||||||
),
|
),
|
||||||
#[cfg(feature = "image_rs")]
|
#[cfg(feature = "image_rs")]
|
||||||
Error::ImageError(e) => {
|
Error::ImageError(e) => {
|
||||||
write!(f, "Unable to create icon from a file: {:?}", e)
|
write!(f, "Unable to create icon from a file: {e:?}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -50,8 +50,8 @@ impl Profiler {
|
||||||
.to_str()
|
.to_str()
|
||||||
.unwrap_or("trace");
|
.unwrap_or("trace");
|
||||||
|
|
||||||
let path = out_dir
|
let path =
|
||||||
.join(format!("{}_trace_{}.json", curr_exe_name, time));
|
out_dir.join(format!("{curr_exe_name}_trace_{time}.json"));
|
||||||
|
|
||||||
layer = layer.file(path);
|
layer = layer.file(path);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -119,8 +119,7 @@ impl Window {
|
||||||
.with_decorations(self.decorations)
|
.with_decorations(self.decorations)
|
||||||
.with_transparent(self.transparent)
|
.with_transparent(self.transparent)
|
||||||
.with_window_icon(self.icon)
|
.with_window_icon(self.icon)
|
||||||
.with_always_on_top(self.always_on_top)
|
.with_always_on_top(self.always_on_top);
|
||||||
.with_visible(self.visible);
|
|
||||||
|
|
||||||
if let Some(position) = conversion::position(
|
if let Some(position) = conversion::position(
|
||||||
primary_monitor.as_ref(),
|
primary_monitor.as_ref(),
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,7 @@ pub fn resize<Message>(
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the window to maximized or back.
|
/// Maximizes the window.
|
||||||
pub fn maximize<Message>(id: window::Id, value: bool) -> Command<Message> {
|
pub fn maximize<Message>(id: window::Id, value: bool) -> Command<Message> {
|
||||||
Command::single(command::Action::Window(
|
Command::single(command::Action::Window(
|
||||||
id,
|
id,
|
||||||
|
|
@ -45,7 +45,7 @@ pub fn maximize<Message>(id: window::Id, value: bool) -> Command<Message> {
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set the window to minimized or back.
|
/// Minimes the window.
|
||||||
pub fn minimize<Message>(id: window::Id, value: bool) -> Command<Message> {
|
pub fn minimize<Message>(id: window::Id, value: bool) -> Command<Message> {
|
||||||
Command::single(command::Action::Window(
|
Command::single(command::Action::Window(
|
||||||
id,
|
id,
|
||||||
|
|
@ -58,16 +58,11 @@ pub fn move_to<Message>(id: window::Id, x: i32, y: i32) -> Command<Message> {
|
||||||
Command::single(command::Action::Window(id, window::Action::Move { x, y }))
|
Command::single(command::Action::Window(id, window::Action::Move { x, y }))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the [`Mode`] of the window.
|
/// Changes the [`Mode`] of the window.
|
||||||
pub fn set_mode<Message>(id: window::Id, mode: Mode) -> Command<Message> {
|
pub fn change_mode<Message>(id: window::Id, mode: Mode) -> Command<Message> {
|
||||||
Command::single(command::Action::Window(id, window::Action::SetMode(mode)))
|
Command::single(command::Action::Window(id, window::Action::SetMode(mode)))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the window to maximized or back.
|
|
||||||
pub fn toggle_maximize<Message>(id: window::Id) -> Command<Message> {
|
|
||||||
Command::single(command::Action::Window(id, window::Action::ToggleMaximize))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Fetches the current [`Mode`] of the window.
|
/// Fetches the current [`Mode`] of the window.
|
||||||
pub fn fetch_mode<Message>(
|
pub fn fetch_mode<Message>(
|
||||||
id: window::Id,
|
id: window::Id,
|
||||||
|
|
@ -78,3 +73,39 @@ pub fn fetch_mode<Message>(
|
||||||
window::Action::FetchMode(Box::new(f)),
|
window::Action::FetchMode(Box::new(f)),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Toggles the window to maximized or back.
|
||||||
|
pub fn toggle_maximize<Message>(id: window::Id) -> Command<Message> {
|
||||||
|
Command::single(command::Action::Window(id, window::Action::ToggleMaximize))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Toggles the window decorations.
|
||||||
|
pub fn toggle_decorations<Message>(id: window::Id) -> Command<Message> {
|
||||||
|
Command::single(command::Action::Window(id, window::Action::ToggleDecorations))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Request user attention to the window, this has no effect if the application
|
||||||
|
/// is already focused. How requesting for user attention manifests is platform dependent,
|
||||||
|
/// see [`UserAttention`] for details.
|
||||||
|
///
|
||||||
|
/// Providing `None` will unset the request for user attention. Unsetting the request for
|
||||||
|
/// user attention might not be done automatically by the WM when the window receives input.
|
||||||
|
pub fn request_user_attention<Message>(
|
||||||
|
id: window::Id,
|
||||||
|
user_attention: Option<UserAttention>,
|
||||||
|
) -> Command<Message> {
|
||||||
|
Command::single(command::Action::Window(
|
||||||
|
id,
|
||||||
|
window::Action::RequestUserAttention(user_attention),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Brings the window to the front and sets input focus. Has no effect if the window is
|
||||||
|
/// already in focus, minimized, or not visible.
|
||||||
|
///
|
||||||
|
/// This [`Command`] steals input focus from other applications. Do not use this method unless
|
||||||
|
/// you are certain that's what the user wants. Focus stealing can cause an extremely disruptive
|
||||||
|
/// user experience.
|
||||||
|
pub fn gain_focus<Message>(id: window::Id) -> Command<Message> {
|
||||||
|
Command::single(command::Action::Window(id, window::Action::GainFocus))
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue