diff options
Diffstat (limited to 'examples')
-rw-r--r-- | examples/events/src/main.rs | 8 | ||||
-rw-r--r-- | examples/integration_opengl/src/main.rs | 1 | ||||
-rw-r--r-- | examples/integration_wgpu/src/main.rs | 5 | ||||
-rw-r--r-- | examples/multi_window/Cargo.toml | 13 | ||||
-rw-r--r-- | examples/multi_window/src/main.rs | 622 | ||||
-rw-r--r-- | examples/solar_system/src/main.rs | 2 |
6 files changed, 644 insertions, 7 deletions
diff --git a/examples/events/src/main.rs b/examples/events/src/main.rs index 1b97018e..b57010c7 100644 --- a/examples/events/src/main.rs +++ b/examples/events/src/main.rs @@ -25,7 +25,7 @@ struct Events { enum Message { EventOccurred(iced_native::Event), Toggled(bool), - Exit, + Exit(window::Id), } impl Application for Events { @@ -54,8 +54,8 @@ impl Application for Events { Command::none() } Message::EventOccurred(event) => { - if let Event::Window(window::Event::CloseRequested) = event { - window::close() + if let Event::Window(id, window::Event::CloseRequested) = event { + window::close(id) } else { Command::none() } @@ -65,7 +65,7 @@ impl Application for Events { Command::none() } - Message::Exit => window::close(), + Message::Exit(id) => window::close(id), } } diff --git a/examples/integration_opengl/src/main.rs b/examples/integration_opengl/src/main.rs index f161c8a0..4dd3a4a9 100644 --- a/examples/integration_opengl/src/main.rs +++ b/examples/integration_opengl/src/main.rs @@ -107,6 +107,7 @@ pub fn main() { // Map window event to iced event if let Some(event) = iced_winit::conversion::window_event( + iced_winit::window::Id::MAIN, &event, windowed_context.window().scale_factor(), modifiers, diff --git a/examples/integration_wgpu/src/main.rs b/examples/integration_wgpu/src/main.rs index 2a56b6fa..1f42013b 100644 --- a/examples/integration_wgpu/src/main.rs +++ b/examples/integration_wgpu/src/main.rs @@ -6,8 +6,8 @@ use scene::Scene; use iced_wgpu::{wgpu, Backend, Renderer, Settings, Viewport}; use iced_winit::{ - conversion, futures, program, renderer, winit, Clipboard, Color, Debug, - Size, + conversion, futures, program, renderer, window, winit, Clipboard, Color, + Debug, Size, }; use winit::{ @@ -169,6 +169,7 @@ pub fn main() { // Map window event to iced event if let Some(event) = iced_winit::conversion::window_event( + window::Id::MAIN, &event, window.scale_factor(), modifiers, diff --git a/examples/multi_window/Cargo.toml b/examples/multi_window/Cargo.toml new file mode 100644 index 00000000..0bb83f37 --- /dev/null +++ b/examples/multi_window/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "multi_window" +version = "0.1.0" +authors = ["Richard Custodio <richardsoncusto@gmail.com>"] +edition = "2021" +publish = false +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +iced = { path = "../..", features = ["debug", "multi_window", "tokio"] } +env_logger = "0.10.0" +iced_native = { path = "../../native" } +iced_lazy = { path = "../../lazy" } diff --git a/examples/multi_window/src/main.rs b/examples/multi_window/src/main.rs new file mode 100644 index 00000000..60f32a7d --- /dev/null +++ b/examples/multi_window/src/main.rs @@ -0,0 +1,622 @@ +use iced::alignment::{self, Alignment}; +use iced::{executor, time}; +use iced::keyboard; +use iced::multi_window::Application; +use iced::theme::{self, Theme}; +use iced::widget::pane_grid::{self, PaneGrid}; +use iced::widget::{ + button, column, container, pick_list, row, scrollable, text, text_input, +}; +use iced::window; +use iced::{Color, Command, Element, Length, Settings, Size, Subscription}; +use iced_lazy::responsive; +use iced_native::{event, subscription, Event}; + +use iced_native::widget::scrollable::{Properties, RelativeOffset}; +use iced_native::window::Id; +use std::collections::HashMap; +use std::time::{Duration, Instant}; + +pub fn main() -> iced::Result { + env_logger::init(); + + Example::run(Settings::default()) +} + +struct Example { + windows: HashMap<window::Id, Window>, + panes_created: usize, + count: usize, + _focused: window::Id, +} + +#[derive(Debug)] +struct Window { + title: String, + scale: f64, + panes: pane_grid::State<Pane>, + focus: Option<pane_grid::Pane>, +} + +#[derive(Debug, Clone)] +enum Message { + Window(window::Id, WindowMessage), + CountIncremented(Instant), +} + +#[derive(Debug, Clone)] +enum WindowMessage { + Split(pane_grid::Axis, pane_grid::Pane), + SplitFocused(pane_grid::Axis), + FocusAdjacent(pane_grid::Direction), + Clicked(pane_grid::Pane), + Dragged(pane_grid::DragEvent), + PopOut(pane_grid::Pane), + Resized(pane_grid::ResizeEvent), + TitleChanged(String), + ToggleMoving(pane_grid::Pane), + TogglePin(pane_grid::Pane), + Close(pane_grid::Pane), + CloseFocused, + SelectedWindow(pane_grid::Pane, SelectableWindow), + CloseWindow, + SnapToggle, +} + +impl Application for Example { + type Executor = executor::Default; + type Message = Message; + type Theme = Theme; + type Flags = (); + + fn new(_flags: ()) -> (Self, Command<Message>) { + let (panes, _) = + pane_grid::State::new(Pane::new(0, pane_grid::Axis::Horizontal)); + let window = Window { + panes, + focus: None, + title: String::from("Default window"), + scale: 1.0, + }; + + ( + Example { + windows: HashMap::from([(window::Id::MAIN, window)]), + panes_created: 1, + count: 0, + _focused: window::Id::MAIN, + }, + Command::none(), + ) + } + + fn title(&self, window: window::Id) -> String { + self.windows + .get(&window) + .map(|w| w.title.clone()) + .unwrap_or(String::from("New Window")) + } + + fn update(&mut self, message: Message) -> Command<Message> { + match message { + Message::Window(id, message) => match message { + WindowMessage::SnapToggle => { + let window = self.windows.get_mut(&id).unwrap(); + + if let Some(focused) = &window.focus { + let pane = window.panes.get_mut(focused).unwrap(); + + let cmd = scrollable::snap_to( + pane.scrollable_id.clone(), + if pane.snapped { + RelativeOffset::START + } else { + RelativeOffset::END + }, + ); + + pane.snapped = !pane.snapped; + return cmd; + } + } + WindowMessage::Split(axis, pane) => { + let window = self.windows.get_mut(&id).unwrap(); + let result = window.panes.split( + axis, + &pane, + Pane::new(self.panes_created, axis), + ); + + if let Some((pane, _)) = result { + window.focus = Some(pane); + } + + self.panes_created += 1; + } + WindowMessage::SplitFocused(axis) => { + let window = self.windows.get_mut(&id).unwrap(); + if let Some(pane) = window.focus { + let result = window.panes.split( + axis, + &pane, + Pane::new(self.panes_created, axis), + ); + + if let Some((pane, _)) = result { + window.focus = Some(pane); + } + + self.panes_created += 1; + } + } + WindowMessage::FocusAdjacent(direction) => { + let window = self.windows.get_mut(&id).unwrap(); + if let Some(pane) = window.focus { + if let Some(adjacent) = + window.panes.adjacent(&pane, direction) + { + window.focus = Some(adjacent); + } + } + } + WindowMessage::Clicked(pane) => { + let window = self.windows.get_mut(&id).unwrap(); + window.focus = Some(pane); + } + WindowMessage::CloseWindow => { + let _ = self.windows.remove(&id); + return window::close(id); + } + WindowMessage::Resized(pane_grid::ResizeEvent { split, ratio }) => { + let window = self.windows.get_mut(&id).unwrap(); + window.panes.resize(&split, ratio); + } + WindowMessage::SelectedWindow(pane, selected) => { + let window = self.windows.get_mut(&id).unwrap(); + let (mut pane, _) = window.panes.close(&pane).unwrap(); + pane.is_moving = false; + + if let Some(window) = self.windows.get_mut(&selected.0) { + let (&first_pane, _) = window.panes.iter().next().unwrap(); + let result = + window.panes.split(pane.axis, &first_pane, pane); + + if let Some((pane, _)) = result { + window.focus = Some(pane); + } + } + } + WindowMessage::ToggleMoving(pane) => { + let window = self.windows.get_mut(&id).unwrap(); + if let Some(pane) = window.panes.get_mut(&pane) { + pane.is_moving = !pane.is_moving; + } + } + WindowMessage::TitleChanged(title) => { + let window = self.windows.get_mut(&id).unwrap(); + window.title = title; + } + WindowMessage::PopOut(pane) => { + let window = self.windows.get_mut(&id).unwrap(); + if let Some((popped, sibling)) = window.panes.close(&pane) { + window.focus = Some(sibling); + + let (panes, _) = pane_grid::State::new(popped); + let window = Window { + panes, + focus: None, + title: format!("New window ({})", self.windows.len()), + scale: 1.0 + (self.windows.len() as f64 / 10.0), + }; + + let window_id = window::Id::new(self.windows.len()); + self.windows.insert(window_id, window); + return window::spawn(window_id, Default::default()); + } + } + WindowMessage::Dragged(pane_grid::DragEvent::Dropped { + pane, + target, + }) => { + let window = self.windows.get_mut(&id).unwrap(); + window.panes.swap(&pane, &target); + } + // WindowMessage::Dragged(pane_grid::DragEvent::Picked { pane }) => { + // println!("Picked {pane:?}"); + // } + WindowMessage::Dragged(_) => {} + WindowMessage::TogglePin(pane) => { + let window = self.windows.get_mut(&id).unwrap(); + if let Some(Pane { is_pinned, .. }) = + window.panes.get_mut(&pane) + { + *is_pinned = !*is_pinned; + } + } + WindowMessage::Close(pane) => { + let window = self.windows.get_mut(&id).unwrap(); + if let Some((_, sibling)) = window.panes.close(&pane) { + window.focus = Some(sibling); + } + } + WindowMessage::CloseFocused => { + let window = self.windows.get_mut(&id).unwrap(); + if let Some(pane) = window.focus { + if let Some(Pane { is_pinned, .. }) = + window.panes.get(&pane) + { + if !is_pinned { + if let Some((_, sibling)) = + window.panes.close(&pane) + { + window.focus = Some(sibling); + } + } + } + } + } + }, + Message::CountIncremented(_) => { + self.count += 1; + } + } + + Command::none() + } + + fn subscription(&self) -> Subscription<Message> { + Subscription::batch(vec![ + subscription::events_with(|event, status| { + if let event::Status::Captured = status { + return None; + } + + match event { + Event::Keyboard(keyboard::Event::KeyPressed { + modifiers, + key_code, + }) if modifiers.command() => { + handle_hotkey(key_code).map(|message| { + Message::Window(window::Id::new(0usize), message) + }) + } // TODO(derezzedex) + _ => None, + } + }), + time::every(Duration::from_secs(1)).map(Message::CountIncremented), + ]) + } + + fn view(&self, window_id: window::Id) -> Element<Message> { + if let Some(window) = self.windows.get(&window_id) { + let focus = window.focus; + let total_panes = window.panes.len(); + + let window_controls = row![ + text_input( + "Window title", + &window.title, + WindowMessage::TitleChanged, + ), + button(text("Close")) + .on_press(WindowMessage::CloseWindow) + .style(theme::Button::Destructive), + ] + .spacing(5) + .align_items(Alignment::Center); + + let pane_grid = PaneGrid::new(&window.panes, |id, pane, _| { + let is_focused = focus == Some(id); + + let pin_button = button( + text(if pane.is_pinned { "Unpin" } else { "Pin" }).size(14), + ) + .on_press(WindowMessage::TogglePin(id)) + .padding(3); + + let title = row![ + pin_button, + "Pane", + text(pane.id.to_string()).style(if is_focused { + PANE_ID_COLOR_FOCUSED + } else { + PANE_ID_COLOR_UNFOCUSED + }), + ] + .spacing(5); + + let title_bar = pane_grid::TitleBar::new(title) + .controls(view_controls( + id, + total_panes, + pane.is_pinned, + pane.is_moving, + &window.title, + window_id, + &self.windows, + )) + .padding(10) + .style(if is_focused { + style::title_bar_focused + } else { + style::title_bar_active + }); + + pane_grid::Content::new(responsive(move |size| { + view_content( + id, + pane.scrollable_id.clone(), + self.count, + total_panes, + pane.is_pinned, + size, + ) + })) + .title_bar(title_bar) + .style(if is_focused { + style::pane_focused + } else { + style::pane_active + }) + }) + .width(Length::Fill) + .height(Length::Fill) + .spacing(10) + .on_click(WindowMessage::Clicked) + .on_drag(WindowMessage::Dragged) + .on_resize(10, WindowMessage::Resized); + + let content: Element<_> = column![window_controls, pane_grid] + .width(Length::Fill) + .height(Length::Fill) + .padding(10) + .into(); + + return content + .map(move |message| Message::Window(window_id, message)); + } + + container(text("This shouldn't be possible!").size(20)) + .center_x() + .center_y() + .into() + } + + fn close_requested(&self, window: window::Id) -> Self::Message { + Message::Window(window, WindowMessage::CloseWindow) + } + + fn scale_factor(&self, window: Id) -> f64 { + self.windows.get(&window).map(|w| w.scale).unwrap_or(1.0) + } +} + +const PANE_ID_COLOR_UNFOCUSED: Color = Color::from_rgb( + 0xFF as f32 / 255.0, + 0xC7 as f32 / 255.0, + 0xC7 as f32 / 255.0, +); +const PANE_ID_COLOR_FOCUSED: Color = Color::from_rgb( + 0xFF as f32 / 255.0, + 0x47 as f32 / 255.0, + 0x47 as f32 / 255.0, +); + +fn handle_hotkey(key_code: keyboard::KeyCode) -> Option<WindowMessage> { + use keyboard::KeyCode; + use pane_grid::{Axis, Direction}; + + let direction = match key_code { + KeyCode::Up => Some(Direction::Up), + KeyCode::Down => Some(Direction::Down), + KeyCode::Left => Some(Direction::Left), + KeyCode::Right => Some(Direction::Right), + _ => None, + }; + + match key_code { + KeyCode::V => Some(WindowMessage::SplitFocused(Axis::Vertical)), + KeyCode::H => Some(WindowMessage::SplitFocused(Axis::Horizontal)), + KeyCode::W => Some(WindowMessage::CloseFocused), + _ => direction.map(WindowMessage::FocusAdjacent), + } +} + +#[derive(Debug, Clone)] +struct SelectableWindow(window::Id, String); + +impl PartialEq for SelectableWindow { + fn eq(&self, other: &Self) -> bool { + self.0 == other.0 + } +} + +impl Eq for SelectableWindow {} + +impl std::fmt::Display for SelectableWindow { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.1.fmt(f) + } +} + +#[derive(Debug)] +struct Pane { + id: usize, + pub scrollable_id: scrollable::Id, + pub axis: pane_grid::Axis, + pub is_pinned: bool, + pub is_moving: bool, + pub snapped: bool, +} + +impl Pane { + fn new(id: usize, axis: pane_grid::Axis) -> Self { + Self { + id, + scrollable_id: scrollable::Id::unique(), + axis, + is_pinned: false, + is_moving: false, + snapped: false, + } + } +} + +fn view_content<'a>( + pane: pane_grid::Pane, + scrollable_id: scrollable::Id, + count: usize, + total_panes: usize, + is_pinned: bool, + size: Size, +) -> Element<'a, WindowMessage> { + let button = |label, message| { + button( + text(label) + .width(Length::Fill) + .horizontal_alignment(alignment::Horizontal::Center) + .size(16), + ) + .width(Length::Fill) + .padding(8) + .on_press(message) + }; + + let mut controls = column![ + button( + "Split horizontally", + WindowMessage::Split(pane_grid::Axis::Horizontal, pane), + ), + button( + "Split vertically", + WindowMessage::Split(pane_grid::Axis::Vertical, pane), + ), + button("Snap", WindowMessage::SnapToggle,) + ] + .spacing(5) + .max_width(150); + + if total_panes > 1 && !is_pinned { + controls = controls.push( + button("Close", WindowMessage::Close(pane)) + .style(theme::Button::Destructive), + ); + } + + let content = column![ + text(format!("{}x{}", size.width, size.height)).size(24), + controls, + text(format!("{count}")).size(48), + ] + .width(Length::Fill) + .height(800) + .spacing(10) + .align_items(Alignment::Center); + + container( + scrollable(content) + .height(Length::Fill) + .vertical_scroll(Properties::new()) + .id(scrollable_id), + ) + .width(Length::Fill) + .height(Length::Fill) + .padding(5) + .center_y() + .into() +} + +fn view_controls<'a>( + pane: pane_grid::Pane, + total_panes: usize, + is_pinned: bool, + is_moving: bool, + window_title: &'a str, + window_id: window::Id, + windows: &HashMap<window::Id, Window>, +) -> Element<'a, WindowMessage> { + let window_selector = { + let options: Vec<_> = windows + .iter() + .map(|(id, window)| SelectableWindow(*id, window.title.clone())) + .collect(); + pick_list( + options, + Some(SelectableWindow(window_id, window_title.to_string())), + move |window| WindowMessage::SelectedWindow(pane, window), + ) + }; + + let mut move_to = button(text("Move to").size(14)).padding(3); + + let mut pop_out = button(text("Pop Out").size(14)).padding(3); + + let mut close = button(text("Close").size(14)) + .style(theme::Button::Destructive) + .padding(3); + + if total_panes > 1 && !is_pinned { + close = close.on_press(WindowMessage::Close(pane)); + pop_out = pop_out.on_press(WindowMessage::PopOut(pane)); + } + + if windows.len() > 1 && total_panes > 1 && !is_pinned { + move_to = move_to.on_press(WindowMessage::ToggleMoving(pane)); + } + + let mut content = row![].spacing(10); + if is_moving { + content = content.push(pop_out).push(window_selector).push(close); + } else { + content = content.push(pop_out).push(move_to).push(close); + } + + content.into() +} + +mod style { + use iced::widget::container; + use iced::Theme; + + pub fn title_bar_active(theme: &Theme) -> container::Appearance { + let palette = theme.extended_palette(); + + container::Appearance { + text_color: Some(palette.background.strong.text), + background: Some(palette.background.strong.color.into()), + ..Default::default() + } + } + + pub fn title_bar_focused(theme: &Theme) -> container::Appearance { + let palette = theme.extended_palette(); + + container::Appearance { + text_color: Some(palette.primary.strong.text), + background: Some(palette.primary.strong.color.into()), + ..Default::default() + } + } + + pub fn pane_active(theme: &Theme) -> container::Appearance { + let palette = theme.extended_palette(); + + container::Appearance { + background: Some(palette.background.weak.color.into()), + border_width: 2.0, + border_color: palette.background.strong.color, + ..Default::default() + } + } + + pub fn pane_focused(theme: &Theme) -> container::Appearance { + let palette = theme.extended_palette(); + + container::Appearance { + background: Some(palette.background.weak.color.into()), + border_width: 2.0, + border_color: palette.primary.strong.color, + ..Default::default() + } + } +} diff --git a/examples/solar_system/src/main.rs b/examples/solar_system/src/main.rs index 9a4ee754..eb461bb0 100644 --- a/examples/solar_system/src/main.rs +++ b/examples/solar_system/src/main.rs @@ -89,7 +89,7 @@ impl Application for SolarSystem { } fn subscription(&self) -> Subscription<Message> { - window::frames().map(Message::Tick) + window::frames().map(|frame| Message::Tick(frame.at)) } } |