diff options
author | 2021-07-22 12:37:39 -0500 | |
---|---|---|
committer | 2021-07-22 12:37:39 -0500 | |
commit | e822f654e44d2d7375b7fda966bb772055f377d4 (patch) | |
tree | 8707561f1bb09c9e58cc9d9884bfb16d956f9f65 /examples | |
parent | 1c06920158e1a47977b2762bf8b34e56fd1a935a (diff) | |
parent | dc0b96ce407283f2ffd9add5ad339f89097555d3 (diff) | |
download | iced-e822f654e44d2d7375b7fda966bb772055f377d4.tar.gz iced-e822f654e44d2d7375b7fda966bb772055f377d4.tar.bz2 iced-e822f654e44d2d7375b7fda966bb772055f377d4.zip |
Merge branch 'master' of https://github.com/hecrj/iced into wgpu_outdatedframe
Diffstat (limited to 'examples')
33 files changed, 1040 insertions, 217 deletions
diff --git a/examples/README.md b/examples/README.md index 32ccf724..10c28cf5 100644 --- a/examples/README.md +++ b/examples/README.md @@ -118,7 +118,7 @@ cargo run --package <example> [Ghostscript Tiger]: https://commons.wikimedia.org/wiki/File:Ghostscript_Tiger.svg ## [Coffee] -Since [Iced was born in May], it has been powering the user interfaces in +Since [Iced was born in May 2019], it has been powering the user interfaces in [Coffee], an experimental 2D game engine. @@ -128,6 +128,6 @@ Since [Iced was born in May], it has been powering the user interfaces in </a> </div> -[Iced was born in May]: https://github.com/hecrj/coffee/pull/35 +[Iced was born in May 2019]: https://github.com/hecrj/coffee/pull/35 [`ui` module]: https://docs.rs/coffee/0.3.2/coffee/ui/index.html [Coffee]: https://github.com/hecrj/coffee diff --git a/examples/clock/src/main.rs b/examples/clock/src/main.rs index b317ac00..9bcc827b 100644 --- a/examples/clock/src/main.rs +++ b/examples/clock/src/main.rs @@ -1,7 +1,7 @@ use iced::{ canvas::{self, Cache, Canvas, Cursor, Geometry, LineCap, Path, Stroke}, - executor, time, Application, Color, Command, Container, Element, Length, - Point, Rectangle, Settings, Subscription, Vector, + executor, time, Application, Clipboard, Color, Command, Container, Element, + Length, Point, Rectangle, Settings, Subscription, Vector, }; pub fn main() -> iced::Result { @@ -40,7 +40,11 @@ impl Application for Clock { String::from("Clock - Iced") } - fn update(&mut self, message: Message) -> Command<Message> { + fn update( + &mut self, + message: Message, + _clipboard: &mut Clipboard, + ) -> Command<Message> { match message { Message::Tick(local_time) => { let now = local_time; diff --git a/examples/download_progress/Cargo.toml b/examples/download_progress/Cargo.toml index 4b05e7dc..d3c578b1 100644 --- a/examples/download_progress/Cargo.toml +++ b/examples/download_progress/Cargo.toml @@ -1,12 +1,12 @@ [package] name = "download_progress" version = "0.1.0" -authors = ["Songtronix <contact@songtronix.com>"] +authors = ["Songtronix <contact@songtronix.com>", "Folyd <lyshuhow@gmail.com>"] edition = "2018" publish = false [dependencies] -iced = { path = "../..", features = ["tokio_old"] } +iced = { path = "../..", features = ["tokio"] } iced_native = { path = "../../native" } iced_futures = { path = "../../futures" } -reqwest = "0.10" +reqwest = "0.11" diff --git a/examples/download_progress/README.md b/examples/download_progress/README.md index c606c5f9..7999ce94 100644 --- a/examples/download_progress/README.md +++ b/examples/download_progress/README.md @@ -1,6 +1,6 @@ ## Download progress -A basic application that asynchronously downloads a dummy file of 100 MB and tracks the download progress. +A basic application that asynchronously downloads multiple dummy files of 100 MB and tracks the download progress. The example implements a custom `Subscription` in the __[`download`](src/download.rs)__ module. This subscription downloads and produces messages that can be used to keep track of its progress. diff --git a/examples/download_progress/src/download.rs b/examples/download_progress/src/download.rs index f46a01f7..08805f13 100644 --- a/examples/download_progress/src/download.rs +++ b/examples/download_progress/src/download.rs @@ -1,37 +1,46 @@ use iced_futures::futures; +use std::hash::{Hash, Hasher}; // Just a little utility function -pub fn file<T: ToString>(url: T) -> iced::Subscription<Progress> { +pub fn file<I: 'static + Hash + Copy + Send, T: ToString>( + id: I, + url: T, +) -> iced::Subscription<(I, Progress)> { iced::Subscription::from_recipe(Download { + id, url: url.to_string(), }) } -pub struct Download { +pub struct Download<I> { + id: I, url: String, } // Make sure iced can use our download stream -impl<H, I> iced_native::subscription::Recipe<H, I> for Download +impl<H, I, T> iced_native::subscription::Recipe<H, I> for Download<T> where - H: std::hash::Hasher, + T: 'static + Hash + Copy + Send, + H: Hasher, { - type Output = Progress; + type Output = (T, Progress); fn hash(&self, state: &mut H) { - use std::hash::Hash; + struct Marker; + std::any::TypeId::of::<Marker>().hash(state); - std::any::TypeId::of::<Self>().hash(state); - self.url.hash(state); + self.id.hash(state); } fn stream( self: Box<Self>, _input: futures::stream::BoxStream<'static, I>, ) -> futures::stream::BoxStream<'static, Self::Output> { + let id = self.id; + Box::pin(futures::stream::unfold( State::Ready(self.url), - |state| async move { + move |state| async move { match state { State::Ready(url) => { let response = reqwest::get(&url).await; @@ -40,7 +49,7 @@ where Ok(response) => { if let Some(total) = response.content_length() { Some(( - Progress::Started, + (id, Progress::Started), State::Downloading { response, total, @@ -48,11 +57,14 @@ where }, )) } else { - Some((Progress::Errored, State::Finished)) + Some(( + (id, Progress::Errored), + State::Finished, + )) } } Err(_) => { - Some((Progress::Errored, State::Finished)) + Some(((id, Progress::Errored), State::Finished)) } } } @@ -68,7 +80,7 @@ where (downloaded as f32 / total as f32) * 100.0; Some(( - Progress::Advanced(percentage), + (id, Progress::Advanced(percentage)), State::Downloading { response, total, @@ -76,8 +88,12 @@ where }, )) } - Ok(None) => Some((Progress::Finished, State::Finished)), - Err(_) => Some((Progress::Errored, State::Finished)), + Ok(None) => { + Some(((id, Progress::Finished), State::Finished)) + } + Err(_) => { + Some(((id, Progress::Errored), State::Finished)) + } }, State::Finished => { // We do not let the stream die, as it would start a diff --git a/examples/download_progress/src/main.rs b/examples/download_progress/src/main.rs index 77b01354..6f844e66 100644 --- a/examples/download_progress/src/main.rs +++ b/examples/download_progress/src/main.rs @@ -1,6 +1,6 @@ use iced::{ - button, executor, Align, Application, Button, Column, Command, Container, - Element, Length, ProgressBar, Settings, Subscription, Text, + button, executor, Align, Application, Button, Clipboard, Column, Command, + Container, Element, Length, ProgressBar, Settings, Subscription, Text, }; mod download; @@ -10,17 +10,17 @@ pub fn main() -> iced::Result { } #[derive(Debug)] -enum Example { - Idle { button: button::State }, - Downloading { progress: f32 }, - Finished { button: button::State }, - Errored { button: button::State }, +struct Example { + downloads: Vec<Download>, + last_id: usize, + add: button::State, } #[derive(Debug, Clone)] pub enum Message { - Download, - DownloadProgressed(download::Progress), + Add, + Download(usize), + DownloadProgressed((usize, download::Progress)), } impl Application for Example { @@ -30,8 +30,10 @@ impl Application for Example { fn new(_flags: ()) -> (Example, Command<Message>) { ( - Example::Idle { - button: button::State::new(), + Example { + downloads: vec![Download::new(0)], + last_id: 0, + add: button::State::new(), }, Command::none(), ) @@ -41,104 +43,177 @@ impl Application for Example { String::from("Download progress - Iced") } - fn update(&mut self, message: Message) -> Command<Message> { + fn update( + &mut self, + message: Message, + _clipboard: &mut Clipboard, + ) -> Command<Message> { match message { - Message::Download => match self { - Example::Idle { .. } - | Example::Finished { .. } - | Example::Errored { .. } => { - *self = Example::Downloading { progress: 0.0 }; + Message::Add => { + self.last_id = self.last_id + 1; + + self.downloads.push(Download::new(self.last_id)); + } + Message::Download(index) => { + if let Some(download) = self.downloads.get_mut(index) { + download.start(); } - _ => {} - }, - Message::DownloadProgressed(message) => match self { - Example::Downloading { progress } => match message { - download::Progress::Started => { - *progress = 0.0; - } - download::Progress::Advanced(percentage) => { - *progress = percentage; - } - download::Progress::Finished => { - *self = Example::Finished { - button: button::State::new(), - } - } - download::Progress::Errored => { - *self = Example::Errored { - button: button::State::new(), - }; - } - }, - _ => {} - }, + } + Message::DownloadProgressed((id, progress)) => { + if let Some(download) = + self.downloads.iter_mut().find(|download| download.id == id) + { + download.progress(progress); + } + } }; Command::none() } fn subscription(&self) -> Subscription<Message> { - match self { - Example::Downloading { .. } => { - download::file("https://speed.hetzner.de/100MB.bin") + Subscription::batch(self.downloads.iter().map(Download::subscription)) + } + + fn view(&mut self) -> Element<Message> { + let downloads = self + .downloads + .iter_mut() + .fold(Column::new().spacing(20), |column, download| { + column.push(download.view()) + }) + .push( + Button::new(&mut self.add, Text::new("Add another download")) + .on_press(Message::Add) + .padding(10), + ) + .align_items(Align::End); + + Container::new(downloads) + .width(Length::Fill) + .height(Length::Fill) + .center_x() + .center_y() + .padding(20) + .into() + } +} + +#[derive(Debug)] +struct Download { + id: usize, + state: State, +} + +#[derive(Debug)] +enum State { + Idle { button: button::State }, + Downloading { progress: f32 }, + Finished { button: button::State }, + Errored { button: button::State }, +} + +impl Download { + pub fn new(id: usize) -> Self { + Download { + id, + state: State::Idle { + button: button::State::new(), + }, + } + } + + pub fn start(&mut self) { + match self.state { + State::Idle { .. } + | State::Finished { .. } + | State::Errored { .. } => { + self.state = State::Downloading { progress: 0.0 }; + } + _ => {} + } + } + + pub fn progress(&mut self, new_progress: download::Progress) { + match &mut self.state { + State::Downloading { progress } => match new_progress { + download::Progress::Started => { + *progress = 0.0; + } + download::Progress::Advanced(percentage) => { + *progress = percentage; + } + download::Progress::Finished => { + self.state = State::Finished { + button: button::State::new(), + } + } + download::Progress::Errored => { + self.state = State::Errored { + button: button::State::new(), + }; + } + }, + _ => {} + } + } + + pub fn subscription(&self) -> Subscription<Message> { + match self.state { + State::Downloading { .. } => { + download::file(self.id, "https://speed.hetzner.de/100MB.bin?") .map(Message::DownloadProgressed) } _ => Subscription::none(), } } - fn view(&mut self) -> Element<Message> { - let current_progress = match self { - Example::Idle { .. } => 0.0, - Example::Downloading { progress } => *progress, - Example::Finished { .. } => 100.0, - Example::Errored { .. } => 0.0, + pub fn view(&mut self) -> Element<Message> { + let current_progress = match &self.state { + State::Idle { .. } => 0.0, + State::Downloading { progress } => *progress, + State::Finished { .. } => 100.0, + State::Errored { .. } => 0.0, }; let progress_bar = ProgressBar::new(0.0..=100.0, current_progress); - let control: Element<_> = match self { - Example::Idle { button } => { + let control: Element<_> = match &mut self.state { + State::Idle { button } => { Button::new(button, Text::new("Start the download!")) - .on_press(Message::Download) + .on_press(Message::Download(self.id)) .into() } - Example::Finished { button } => Column::new() + State::Finished { button } => Column::new() .spacing(10) .align_items(Align::Center) .push(Text::new("Download finished!")) .push( Button::new(button, Text::new("Start again")) - .on_press(Message::Download), + .on_press(Message::Download(self.id)), ) .into(), - Example::Downloading { .. } => { + State::Downloading { .. } => { Text::new(format!("Downloading... {:.2}%", current_progress)) .into() } - Example::Errored { button } => Column::new() + State::Errored { button } => Column::new() .spacing(10) .align_items(Align::Center) .push(Text::new("Something went wrong :(")) .push( Button::new(button, Text::new("Try again")) - .on_press(Message::Download), + .on_press(Message::Download(self.id)), ) .into(), }; - let content = Column::new() + Column::new() .spacing(10) .padding(10) .align_items(Align::Center) .push(progress_bar) - .push(control); - - Container::new(content) - .width(Length::Fill) - .height(Length::Fill) - .center_x() - .center_y() + .push(control) .into() } } diff --git a/examples/events/src/main.rs b/examples/events/src/main.rs index 6eba6aad..446c190b 100644 --- a/examples/events/src/main.rs +++ b/examples/events/src/main.rs @@ -1,22 +1,30 @@ use iced::{ - executor, Align, Application, Checkbox, Column, Command, Container, - Element, Length, Settings, Subscription, Text, + button, executor, Align, Application, Button, Checkbox, Clipboard, Column, + Command, Container, Element, HorizontalAlignment, Length, Settings, + Subscription, Text, }; +use iced_native::{window, Event}; pub fn main() -> iced::Result { - Events::run(Settings::default()) + Events::run(Settings { + exit_on_close_request: false, + ..Settings::default() + }) } #[derive(Debug, Default)] struct Events { last: Vec<iced_native::Event>, enabled: bool, + exit: button::State, + should_exit: bool, } #[derive(Debug, Clone)] enum Message { EventOccurred(iced_native::Event), Toggled(bool), + Exit, } impl Application for Events { @@ -32,29 +40,41 @@ impl Application for Events { String::from("Events - Iced") } - fn update(&mut self, message: Message) -> Command<Message> { + fn update( + &mut self, + message: Message, + _clipboard: &mut Clipboard, + ) -> Command<Message> { match message { - Message::EventOccurred(event) => { + Message::EventOccurred(event) if self.enabled => { self.last.push(event); if self.last.len() > 5 { let _ = self.last.remove(0); } } + Message::EventOccurred(event) => { + if let Event::Window(window::Event::CloseRequested) = event { + self.should_exit = true; + } + } Message::Toggled(enabled) => { self.enabled = enabled; } + Message::Exit => { + self.should_exit = true; + } }; Command::none() } fn subscription(&self) -> Subscription<Message> { - if self.enabled { - iced_native::subscription::events().map(Message::EventOccurred) - } else { - Subscription::none() - } + iced_native::subscription::events().map(Message::EventOccurred) + } + + fn should_exit(&self) -> bool { + self.should_exit } fn view(&mut self) -> Element<Message> { @@ -71,11 +91,22 @@ impl Application for Events { Message::Toggled, ); + let exit = Button::new( + &mut self.exit, + Text::new("Exit") + .width(Length::Fill) + .horizontal_alignment(HorizontalAlignment::Center), + ) + .width(Length::Units(100)) + .padding(10) + .on_press(Message::Exit); + let content = Column::new() .align_items(Align::Center) .spacing(20) .push(events) - .push(toggle); + .push(toggle) + .push(exit); Container::new(content) .width(Length::Fill) diff --git a/examples/game_of_life/Cargo.toml b/examples/game_of_life/Cargo.toml index 9c4172c4..ffd2f19e 100644 --- a/examples/game_of_life/Cargo.toml +++ b/examples/game_of_life/Cargo.toml @@ -7,6 +7,6 @@ publish = false [dependencies] iced = { path = "../..", features = ["canvas", "tokio", "debug"] } -tokio = { version = "0.3", features = ["sync"] } +tokio = { version = "1.0", features = ["sync"] } itertools = "0.9" rustc-hash = "1.1" diff --git a/examples/game_of_life/src/main.rs b/examples/game_of_life/src/main.rs index e18bd6e0..c3e16e8b 100644 --- a/examples/game_of_life/src/main.rs +++ b/examples/game_of_life/src/main.rs @@ -6,12 +6,14 @@ mod style; use grid::Grid; use iced::button::{self, Button}; use iced::executor; +use iced::menu::{self, Menu}; use iced::pick_list::{self, PickList}; use iced::slider::{self, Slider}; use iced::time; +use iced::window; use iced::{ - Align, Application, Checkbox, Column, Command, Container, Element, Length, - Row, Settings, Subscription, Text, + Align, Application, Checkbox, Clipboard, Column, Command, Container, + Element, Length, Row, Settings, Subscription, Text, }; use preset::Preset; use std::time::{Duration, Instant}; @@ -19,6 +21,10 @@ use std::time::{Duration, Instant}; pub fn main() -> iced::Result { GameOfLife::run(Settings { antialiasing: true, + window: window::Settings { + position: window::Position::Centered, + ..window::Settings::default() + }, ..Settings::default() }) } @@ -65,7 +71,11 @@ impl Application for GameOfLife { String::from("Game of Life - Iced") } - fn update(&mut self, message: Message) -> Command<Message> { + fn update( + &mut self, + message: Message, + _clipboard: &mut Clipboard, + ) -> Command<Message> { match message { Message::Grid(message, version) => { if version == self.version { @@ -124,6 +134,13 @@ impl Application for GameOfLife { } } + fn menu(&self) -> Menu<Message> { + Menu::with_entries(vec![menu::Entry::dropdown( + "Presets", + Preset::menu().map(Message::PresetPicked), + )]) + } + fn view(&mut self) -> Element<Message> { let version = self.version; let selected_speed = self.next_speed.unwrap_or(self.speed); diff --git a/examples/game_of_life/src/preset.rs b/examples/game_of_life/src/preset.rs index 05157b6a..1c199a72 100644 --- a/examples/game_of_life/src/preset.rs +++ b/examples/game_of_life/src/preset.rs @@ -1,3 +1,5 @@ +use iced::menu::{self, Menu}; + #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Preset { Custom, @@ -26,6 +28,17 @@ pub static ALL: &[Preset] = &[ ]; impl Preset { + pub fn menu() -> Menu<Self> { + Menu::with_entries( + ALL.iter() + .copied() + .map(|preset| { + menu::Entry::item(preset.to_string(), None, preset) + }) + .collect(), + ) + } + pub fn life(self) -> Vec<(isize, isize)> { #[rustfmt::skip] let cells = match self { diff --git a/examples/game_of_life/src/style.rs b/examples/game_of_life/src/style.rs index 6605826f..be9a0e96 100644 --- a/examples/game_of_life/src/style.rs +++ b/examples/game_of_life/src/style.rs @@ -171,6 +171,7 @@ impl pick_list::StyleSheet for PickList { }, border_radius: 2.0, icon_size: 0.5, + ..pick_list::Style::default() } } diff --git a/examples/integration/src/controls.rs b/examples/integration/src/controls.rs index 824f9f53..36ee9b7e 100644 --- a/examples/integration/src/controls.rs +++ b/examples/integration/src/controls.rs @@ -1,7 +1,7 @@ use iced_wgpu::Renderer; use iced_winit::{ - slider, Align, Color, Column, Command, Element, Length, Program, Row, - Slider, Text, + slider, Align, Clipboard, Color, Column, Command, Element, Length, Program, + Row, Slider, Text, }; pub struct Controls { @@ -30,8 +30,13 @@ impl Controls { impl Program for Controls { type Renderer = Renderer; type Message = Message; + type Clipboard = Clipboard; - fn update(&mut self, message: Message) -> Command<Message> { + fn update( + &mut self, + message: Message, + _clipboard: &mut Clipboard, + ) -> Command<Message> { match message { Message::BackgroundColorChanged(color) => { self.background_color = color; diff --git a/examples/integration/src/main.rs b/examples/integration/src/main.rs index 88d8d023..ab0e2299 100644 --- a/examples/integration/src/main.rs +++ b/examples/integration/src/main.rs @@ -5,7 +5,7 @@ use controls::Controls; use scene::Scene; use iced_wgpu::{wgpu, Backend, Renderer, Settings, Viewport}; -use iced_winit::{conversion, futures, program, winit, Debug, Size}; +use iced_winit::{conversion, futures, program, winit, Clipboard, Debug, Size}; use futures::task::SpawnExt; use winit::{ @@ -28,6 +28,7 @@ pub fn main() { ); let mut cursor_position = PhysicalPosition::new(-1.0, -1.0); let mut modifiers = ModifiersState::default(); + let mut clipboard = Clipboard::connect(&window); // Initialize wgpu let instance = wgpu::Instance::new(wgpu::BackendBit::PRIMARY); @@ -36,7 +37,7 @@ pub fn main() { let (mut device, queue) = futures::executor::block_on(async { let adapter = instance .request_adapter(&wgpu::RequestAdapterOptions { - power_preference: wgpu::PowerPreference::Default, + power_preference: wgpu::PowerPreference::HighPerformance, compatible_surface: Some(&surface), }) .await @@ -45,9 +46,9 @@ pub fn main() { adapter .request_device( &wgpu::DeviceDescriptor { + label: None, features: wgpu::Features::empty(), limits: wgpu::Limits::default(), - shader_validation: false, }, None, ) @@ -63,7 +64,7 @@ pub fn main() { device.create_swap_chain( &surface, &wgpu::SwapChainDescriptor { - usage: wgpu::TextureUsage::OUTPUT_ATTACHMENT, + usage: wgpu::TextureUsage::RENDER_ATTACHMENT, format: format, width: size.width, height: size.height, @@ -141,8 +142,8 @@ pub fn main() { cursor_position, viewport.scale_factor(), ), - None, &mut renderer, + &mut clipboard, &mut debug, ); @@ -157,7 +158,7 @@ pub fn main() { swap_chain = device.create_swap_chain( &surface, &wgpu::SwapChainDescriptor { - usage: wgpu::TextureUsage::OUTPUT_ATTACHMENT, + usage: wgpu::TextureUsage::RENDER_ATTACHMENT, format: format, width: size.width, height: size.height, diff --git a/examples/integration/src/scene.rs b/examples/integration/src/scene.rs index 03a338c6..3e8277c8 100644 --- a/examples/integration/src/scene.rs +++ b/examples/integration/src/scene.rs @@ -19,8 +19,9 @@ impl Scene { background_color: Color, ) -> wgpu::RenderPass<'a> { encoder.begin_render_pass(&wgpu::RenderPassDescriptor { - color_attachments: &[wgpu::RenderPassColorAttachmentDescriptor { - attachment: target, + label: None, + color_attachments: &[wgpu::RenderPassColorAttachment { + view: target, resolve_target: None, ops: wgpu::Operations { load: wgpu::LoadOp::Clear({ @@ -48,10 +49,10 @@ impl Scene { fn build_pipeline(device: &wgpu::Device) -> wgpu::RenderPipeline { let vs_module = - device.create_shader_module(wgpu::include_spirv!("shader/vert.spv")); + device.create_shader_module(&wgpu::include_spirv!("shader/vert.spv")); let fs_module = - device.create_shader_module(wgpu::include_spirv!("shader/frag.spv")); + device.create_shader_module(&wgpu::include_spirv!("shader/frag.spv")); let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { @@ -64,34 +65,34 @@ fn build_pipeline(device: &wgpu::Device) -> wgpu::RenderPipeline { device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { label: None, layout: Some(&pipeline_layout), - vertex_stage: wgpu::ProgrammableStageDescriptor { + vertex: wgpu::VertexState { module: &vs_module, entry_point: "main", + buffers: &[], }, - fragment_stage: Some(wgpu::ProgrammableStageDescriptor { + fragment: Some(wgpu::FragmentState { module: &fs_module, entry_point: "main", + targets: &[wgpu::ColorTargetState { + format: wgpu::TextureFormat::Bgra8UnormSrgb, + blend: Some(wgpu::BlendState { + color: wgpu::BlendComponent::REPLACE, + alpha: wgpu::BlendComponent::REPLACE, + }), + write_mask: wgpu::ColorWrite::ALL, + }], }), - rasterization_state: Some(wgpu::RasterizationStateDescriptor { + primitive: wgpu::PrimitiveState { + topology: wgpu::PrimitiveTopology::TriangleList, front_face: wgpu::FrontFace::Ccw, - cull_mode: wgpu::CullMode::None, ..Default::default() - }), - primitive_topology: wgpu::PrimitiveTopology::TriangleList, - color_states: &[wgpu::ColorStateDescriptor { - format: wgpu::TextureFormat::Bgra8UnormSrgb, - color_blend: wgpu::BlendDescriptor::REPLACE, - alpha_blend: wgpu::BlendDescriptor::REPLACE, - write_mask: wgpu::ColorWrite::ALL, - }], - depth_stencil_state: None, - vertex_state: wgpu::VertexStateDescriptor { - index_format: wgpu::IndexFormat::Uint16, - vertex_buffers: &[], }, - sample_count: 1, - sample_mask: !0, - alpha_to_coverage_enabled: false, + depth_stencil: None, + multisample: wgpu::MultisampleState { + count: 1, + mask: !0, + alpha_to_coverage_enabled: false, + }, }); pipeline diff --git a/examples/menu/Cargo.toml b/examples/menu/Cargo.toml new file mode 100644 index 00000000..44597734 --- /dev/null +++ b/examples/menu/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "menu" +version = "0.1.0" +authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"] +edition = "2018" +publish = false + +[dependencies] +iced = { path = "../.." } +iced_native = { path = "../../native" }
\ No newline at end of file diff --git a/examples/menu/src/main.rs b/examples/menu/src/main.rs new file mode 100644 index 00000000..7403713c --- /dev/null +++ b/examples/menu/src/main.rs @@ -0,0 +1,117 @@ +use iced::menu::{self, Menu}; +use iced::{ + executor, Application, Clipboard, Command, Container, Element, Length, + Settings, Text, +}; +use iced_native::keyboard::{Hotkey, KeyCode, Modifiers}; + +pub fn main() -> iced::Result { + App::run(Settings::default()) +} + +#[derive(Debug, Default)] +struct App { + selected: Option<Entry>, +} + +#[derive(Debug, Clone)] +enum Entry { + One, + Two, + Three, + A, + B, + C, +} + +#[derive(Debug, Clone)] +enum Message { + MenuActivated(Entry), +} + +impl Application for App { + type Executor = executor::Default; + type Message = Message; + type Flags = (); + + fn new(_flags: ()) -> (App, Command<Message>) { + (App::default(), Command::none()) + } + + fn title(&self) -> String { + String::from("Menu - Iced") + } + + fn menu(&self) -> Menu<Message> { + let alt = Modifiers::ALT; + let ctrl_shift = Modifiers::CTRL | Modifiers::SHIFT; + + Menu::with_entries(vec![ + menu::Entry::dropdown( + "First", + Menu::with_entries(vec![ + menu::Entry::item( + "One", + Hotkey::new(alt, KeyCode::F1), + Message::MenuActivated(Entry::One), + ), + menu::Entry::item( + "Two", + Hotkey::new(alt, KeyCode::F2), + Message::MenuActivated(Entry::Two), + ), + menu::Entry::Separator, + menu::Entry::item( + "Three", + Hotkey::new(alt, KeyCode::F3), + Message::MenuActivated(Entry::Three), + ), + ]), + ), + menu::Entry::dropdown( + "Second", + Menu::with_entries(vec![ + menu::Entry::item( + "A", + Hotkey::new(ctrl_shift, KeyCode::A), + Message::MenuActivated(Entry::A), + ), + menu::Entry::item( + "B", + Hotkey::new(ctrl_shift, KeyCode::B), + Message::MenuActivated(Entry::B), + ), + menu::Entry::Separator, + menu::Entry::item( + "C", + Hotkey::new(ctrl_shift, KeyCode::C), + Message::MenuActivated(Entry::C), + ), + ]), + ), + ]) + } + + fn update( + &mut self, + message: Message, + _clipboard: &mut Clipboard, + ) -> Command<Message> { + match message { + Message::MenuActivated(entry) => self.selected = Some(entry), + } + + Command::none() + } + + fn view(&mut self) -> Element<Message> { + Container::new( + Text::new(format!("Selected {:?}", self.selected)).size(48), + ) + .width(Length::Fill) + .height(Length::Fill) + .center_x() + .center_y() + .into() + } +} diff --git a/examples/pane_grid/src/main.rs b/examples/pane_grid/src/main.rs index 3c3256cf..3bd8aa25 100644 --- a/examples/pane_grid/src/main.rs +++ b/examples/pane_grid/src/main.rs @@ -1,7 +1,8 @@ use iced::{ button, executor, keyboard, pane_grid, scrollable, Align, Application, - Button, Column, Command, Container, Element, HorizontalAlignment, Length, - PaneGrid, Scrollable, Settings, Subscription, Text, + Button, Clipboard, Color, Column, Command, Container, Element, + HorizontalAlignment, Length, PaneGrid, Row, Scrollable, Settings, + Subscription, Text, }; use iced_native::{event, subscription, Event}; @@ -10,7 +11,7 @@ pub fn main() -> iced::Result { } struct Example { - panes: pane_grid::State<Content>, + panes: pane_grid::State<Pane>, panes_created: usize, focus: Option<pane_grid::Pane>, } @@ -23,6 +24,7 @@ enum Message { Clicked(pane_grid::Pane), Dragged(pane_grid::DragEvent), Resized(pane_grid::ResizeEvent), + TogglePin(pane_grid::Pane), Close(pane_grid::Pane), CloseFocused, } @@ -33,7 +35,7 @@ impl Application for Example { type Flags = (); fn new(_flags: ()) -> (Self, Command<Message>) { - let (panes, _) = pane_grid::State::new(Content::new(0)); + let (panes, _) = pane_grid::State::new(Pane::new(0)); ( Example { @@ -49,13 +51,17 @@ impl Application for Example { String::from("Pane grid - Iced") } - fn update(&mut self, message: Message) -> Command<Message> { + fn update( + &mut self, + message: Message, + _clipboard: &mut Clipboard, + ) -> Command<Message> { match message { Message::Split(axis, pane) => { let result = self.panes.split( axis, &pane, - Content::new(self.panes_created), + Pane::new(self.panes_created), ); if let Some((pane, _)) = result { @@ -69,7 +75,7 @@ impl Application for Example { let result = self.panes.split( axis, &pane, - Content::new(self.panes_created), + Pane::new(self.panes_created), ); if let Some((pane, _)) = result { @@ -101,6 +107,12 @@ impl Application for Example { self.panes.swap(&pane, &target); } Message::Dragged(_) => {} + Message::TogglePin(pane) => { + if let Some(Pane { is_pinned, .. }) = self.panes.get_mut(&pane) + { + *is_pinned = !*is_pinned; + } + } Message::Close(pane) => { if let Some((_, sibling)) = self.panes.close(&pane) { self.focus = Some(sibling); @@ -108,8 +120,14 @@ impl Application for Example { } Message::CloseFocused => { if let Some(pane) = self.focus { - if let Some((_, sibling)) = self.panes.close(&pane) { - self.focus = Some(sibling); + if let Some(Pane { is_pinned, .. }) = self.panes.get(&pane) + { + if !is_pinned { + if let Some((_, sibling)) = self.panes.close(&pane) + { + self.focus = Some(sibling); + } + } } } } @@ -128,7 +146,7 @@ impl Application for Example { Event::Keyboard(keyboard::Event::KeyPressed { modifiers, key_code, - }) if modifiers.is_command_pressed() => handle_hotkey(key_code), + }) if modifiers.command() => handle_hotkey(key_code), _ => None, } }) @@ -138,17 +156,41 @@ impl Application for Example { let focus = self.focus; let total_panes = self.panes.len(); - let pane_grid = PaneGrid::new(&mut self.panes, |pane, content| { - let is_focused = focus == Some(pane); - - let title_bar = - pane_grid::TitleBar::new(format!("Pane {}", content.id)) - .padding(10) - .style(style::TitleBar { is_focused }); - - pane_grid::Content::new(content.view(pane, total_panes)) - .title_bar(title_bar) - .style(style::Pane { is_focused }) + let pane_grid = PaneGrid::new(&mut self.panes, |id, pane| { + let is_focused = focus == Some(id); + + let text = if pane.is_pinned { "Unpin" } else { "Pin" }; + let pin_button = + Button::new(&mut pane.pin_button, Text::new(text).size(14)) + .on_press(Message::TogglePin(id)) + .style(style::Button::Pin) + .padding(3); + + let title = Row::with_children(vec![ + pin_button.into(), + Text::new("Pane").into(), + Text::new(pane.content.id.to_string()) + .color(if is_focused { + PANE_ID_COLOR_FOCUSED + } else { + PANE_ID_COLOR_UNFOCUSED + }) + .into(), + ]) + .spacing(5); + + let title_bar = pane_grid::TitleBar::new(title) + .controls(pane.controls.view(id, total_panes, pane.is_pinned)) + .padding(10) + .style(style::TitleBar { is_focused }); + + pane_grid::Content::new(pane.content.view( + id, + total_panes, + pane.is_pinned, + )) + .title_bar(title_bar) + .style(style::Pane { is_focused }) }) .width(Length::Fill) .height(Length::Fill) @@ -165,6 +207,17 @@ impl Application for Example { } } +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<Message> { use keyboard::KeyCode; use pane_grid::{Axis, Direction}; @@ -185,6 +238,13 @@ fn handle_hotkey(key_code: keyboard::KeyCode) -> Option<Message> { } } +struct Pane { + pub is_pinned: bool, + pub pin_button: button::State, + pub content: Content, + pub controls: Controls, +} + struct Content { id: usize, scroll: scrollable::State, @@ -193,6 +253,21 @@ struct Content { close: button::State, } +struct Controls { + close: button::State, +} + +impl Pane { + fn new(id: usize) -> Self { + Self { + is_pinned: false, + pin_button: button::State::new(), + content: Content::new(id), + controls: Controls::new(), + } + } +} + impl Content { fn new(id: usize) -> Self { Content { @@ -207,6 +282,7 @@ impl Content { &mut self, pane: pane_grid::Pane, total_panes: usize, + is_pinned: bool, ) -> Element<Message> { let Content { scroll, @@ -246,7 +322,7 @@ impl Content { style::Button::Primary, )); - if total_panes > 1 { + if total_panes > 1 && !is_pinned { controls = controls.push(button( close, "Close", @@ -270,7 +346,32 @@ impl Content { } } +impl Controls { + fn new() -> Self { + Self { + close: button::State::new(), + } + } + + pub fn view( + &mut self, + pane: pane_grid::Pane, + total_panes: usize, + is_pinned: bool, + ) -> Element<Message> { + let mut button = + Button::new(&mut self.close, Text::new("Close").size(14)) + .style(style::Button::Control) + .padding(3); + if total_panes > 1 && !is_pinned { + button = button.on_press(Message::Close(pane)); + } + button.into() + } +} + mod style { + use crate::PANE_ID_COLOR_FOCUSED; use iced::{button, container, Background, Color, Vector}; const SURFACE: Color = Color::from_rgb( @@ -332,6 +433,8 @@ mod style { pub enum Button { Primary, Destructive, + Control, + Pin, } impl button::StyleSheet for Button { @@ -341,6 +444,8 @@ mod style { Button::Destructive => { (None, Color::from_rgb8(0xFF, 0x47, 0x47)) } + Button::Control => (Some(PANE_ID_COLOR_FOCUSED), Color::WHITE), + Button::Pin => (Some(ACTIVE), Color::WHITE), }; button::Style { @@ -361,6 +466,8 @@ mod style { a: 0.2, ..active.text_color }), + Button::Control => Some(PANE_ID_COLOR_FOCUSED), + Button::Pin => Some(HOVERED), }; button::Style { diff --git a/examples/pick_list/src/main.rs b/examples/pick_list/src/main.rs index 68662602..1eec9791 100644 --- a/examples/pick_list/src/main.rs +++ b/examples/pick_list/src/main.rs @@ -11,7 +11,7 @@ pub fn main() -> iced::Result { struct Example { scroll: scrollable::State, pick_list: pick_list::State<Language>, - selected_language: Language, + selected_language: Option<Language>, } #[derive(Debug, Clone, Copy)] @@ -33,7 +33,7 @@ impl Sandbox for Example { fn update(&mut self, message: Message) { match message { Message::LanguageSelected(language) => { - self.selected_language = language; + self.selected_language = Some(language); } } } @@ -42,9 +42,10 @@ impl Sandbox for Example { let pick_list = PickList::new( &mut self.pick_list, &Language::ALL[..], - Some(self.selected_language), + self.selected_language, Message::LanguageSelected, - ); + ) + .placeholder("Choose a language..."); let mut content = Scrollable::new(&mut self.scroll) .width(Length::Fill) diff --git a/examples/pokedex/src/main.rs b/examples/pokedex/src/main.rs index 187e5dee..da1d5d5d 100644 --- a/examples/pokedex/src/main.rs +++ b/examples/pokedex/src/main.rs @@ -1,6 +1,6 @@ use iced::{ - button, futures, image, Align, Application, Button, Column, Command, - Container, Element, Image, Length, Row, Settings, Text, + button, futures, image, Align, Application, Button, Clipboard, Column, + Command, Container, Element, Length, Row, Settings, Text, }; pub fn main() -> iced::Result { @@ -48,7 +48,11 @@ impl Application for Pokedex { format!("{} - Pokédex", subtitle) } - fn update(&mut self, message: Message) -> Command<Message> { + fn update( + &mut self, + message: Message, + _clipboard: &mut Clipboard, + ) -> Command<Message> { match message { Message::PokemonFound(Ok(pokemon)) => { *self = Pokedex::Loaded { @@ -112,16 +116,20 @@ struct Pokemon { name: String, description: String, image: image::Handle, + image_viewer: image::viewer::State, } impl Pokemon { const TOTAL: u16 = 807; - fn view(&self) -> Element<Message> { + fn view(&mut self) -> Element<Message> { Row::new() .spacing(20) .align_items(Align::Center) - .push(Image::new(self.image.clone())) + .push(image::Viewer::new( + &mut self.image_viewer, + self.image.clone(), + )) .push( Column::new() .spacing(20) @@ -200,11 +208,15 @@ impl Pokemon { .map(|c| if c.is_control() { ' ' } else { c }) .collect(), image, + image_viewer: image::viewer::State::new(), }) } async fn fetch_image(id: u16) -> Result<image::Handle, reqwest::Error> { - let url = format!("https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/{}.png", id); + let url = format!( + "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/{}.png", + id + ); #[cfg(not(target_arch = "wasm32"))] { diff --git a/examples/scrollable/Cargo.toml b/examples/scrollable/Cargo.toml index 12753fb6..08502458 100644 --- a/examples/scrollable/Cargo.toml +++ b/examples/scrollable/Cargo.toml @@ -6,4 +6,4 @@ edition = "2018" publish = false [dependencies] -iced = { path = "../.." } +iced = { path = "../..", features = ["debug"] } diff --git a/examples/scrollable/src/main.rs b/examples/scrollable/src/main.rs index 8dd2e20c..3416b83d 100644 --- a/examples/scrollable/src/main.rs +++ b/examples/scrollable/src/main.rs @@ -1,8 +1,8 @@ mod style; use iced::{ - scrollable, Column, Container, Element, Length, Radio, Row, Rule, Sandbox, - Scrollable, Settings, Space, Text, + button, scrollable, Button, Column, Container, Element, Length, + ProgressBar, Radio, Row, Rule, Sandbox, Scrollable, Settings, Space, Text, }; pub fn main() -> iced::Result { @@ -17,6 +17,9 @@ struct ScrollableDemo { #[derive(Debug, Clone)] enum Message { ThemeChanged(style::Theme), + ScrollToTop(usize), + ScrollToBottom(usize), + Scrolled(usize, f32), } impl Sandbox for ScrollableDemo { @@ -36,6 +39,25 @@ impl Sandbox for ScrollableDemo { fn update(&mut self, message: Message) { match message { Message::ThemeChanged(theme) => self.theme = theme, + Message::ScrollToTop(i) => { + if let Some(variant) = self.variants.get_mut(i) { + variant.scrollable.snap_to(0.0); + + variant.latest_offset = 0.0; + } + } + Message::ScrollToBottom(i) => { + if let Some(variant) = self.variants.get_mut(i) { + variant.scrollable.snap_to(1.0); + + variant.latest_offset = 1.0; + } + } + Message::Scrolled(i, offset) => { + if let Some(variant) = self.variants.get_mut(i) { + variant.latest_offset = offset; + } + } } } @@ -62,13 +84,28 @@ impl Sandbox for ScrollableDemo { let scrollable_row = Row::with_children( variants .iter_mut() - .map(|variant| { - let mut scrollable = Scrollable::new(&mut variant.state) - .padding(10) - .width(Length::Fill) - .height(Length::Fill) - .style(*theme) - .push(Text::new(variant.title)); + .enumerate() + .map(|(i, variant)| { + let mut scrollable = + Scrollable::new(&mut variant.scrollable) + .padding(10) + .spacing(10) + .width(Length::Fill) + .height(Length::Fill) + .on_scroll(move |offset| { + Message::Scrolled(i, offset) + }) + .style(*theme) + .push(Text::new(variant.title)) + .push( + Button::new( + &mut variant.scroll_to_bottom, + Text::new("Scroll to bottom"), + ) + .width(Length::Fill) + .padding(10) + .on_press(Message::ScrollToBottom(i)), + ); if let Some(scrollbar_width) = variant.scrollbar_width { scrollable = scrollable @@ -108,12 +145,31 @@ impl Sandbox for ScrollableDemo { .push(Space::with_height(Length::Units(1200))) .push(Text::new("Middle")) .push(Space::with_height(Length::Units(1200))) - .push(Text::new("The End.")); - - Container::new(scrollable) + .push(Text::new("The End.")) + .push( + Button::new( + &mut variant.scroll_to_top, + Text::new("Scroll to top"), + ) + .width(Length::Fill) + .padding(10) + .on_press(Message::ScrollToTop(i)), + ); + + Column::new() .width(Length::Fill) .height(Length::Fill) - .style(*theme) + .spacing(10) + .push( + Container::new(scrollable) + .width(Length::Fill) + .height(Length::Fill) + .style(*theme), + ) + .push(ProgressBar::new( + 0.0..=1.0, + variant.latest_offset, + )) .into() }) .collect(), @@ -142,10 +198,13 @@ impl Sandbox for ScrollableDemo { /// A version of a scrollable struct Variant { title: &'static str, - state: scrollable::State, + scrollable: scrollable::State, + scroll_to_top: button::State, + scroll_to_bottom: button::State, scrollbar_width: Option<u16>, scrollbar_margin: Option<u16>, scroller_width: Option<u16>, + latest_offset: f32, } impl Variant { @@ -153,31 +212,43 @@ impl Variant { vec![ Self { title: "Default Scrollbar", - state: scrollable::State::new(), + scrollable: scrollable::State::new(), + scroll_to_top: button::State::new(), + scroll_to_bottom: button::State::new(), scrollbar_width: None, scrollbar_margin: None, scroller_width: None, + latest_offset: 0.0, }, Self { title: "Slimmed & Margin", - state: scrollable::State::new(), + scrollable: scrollable::State::new(), + scroll_to_top: button::State::new(), + scroll_to_bottom: button::State::new(), scrollbar_width: Some(4), scrollbar_margin: Some(3), scroller_width: Some(4), + latest_offset: 0.0, }, Self { title: "Wide Scroller", - state: scrollable::State::new(), + scrollable: scrollable::State::new(), + scroll_to_top: button::State::new(), + scroll_to_bottom: button::State::new(), scrollbar_width: Some(4), scrollbar_margin: None, scroller_width: Some(10), + latest_offset: 0.0, }, Self { title: "Narrow Scroller", - state: scrollable::State::new(), + scrollable: scrollable::State::new(), + scroll_to_top: button::State::new(), + scroll_to_bottom: button::State::new(), scrollbar_width: Some(10), scrollbar_margin: None, scroller_width: Some(4), + latest_offset: 0.0, }, ] } diff --git a/examples/solar_system/Cargo.toml b/examples/solar_system/Cargo.toml index 44ced729..327fe0aa 100644 --- a/examples/solar_system/Cargo.toml +++ b/examples/solar_system/Cargo.toml @@ -7,4 +7,4 @@ publish = false [dependencies] iced = { path = "../..", features = ["canvas", "tokio", "debug"] } -rand = "0.7" +rand = "0.8.3" diff --git a/examples/solar_system/src/main.rs b/examples/solar_system/src/main.rs index 6a2de736..8f844828 100644 --- a/examples/solar_system/src/main.rs +++ b/examples/solar_system/src/main.rs @@ -8,8 +8,8 @@ //! [1]: https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial/Basic_animations#An_animated_solar_system use iced::{ canvas::{self, Cursor, Path, Stroke}, - executor, time, window, Application, Canvas, Color, Command, Element, - Length, Point, Rectangle, Settings, Size, Subscription, Vector, + executor, time, window, Application, Canvas, Clipboard, Color, Command, + Element, Length, Point, Rectangle, Settings, Size, Subscription, Vector, }; use std::time::Instant; @@ -48,7 +48,11 @@ impl Application for SolarSystem { String::from("Solar system - Iced") } - fn update(&mut self, message: Message) -> Command<Message> { + fn update( + &mut self, + message: Message, + _clipboard: &mut Clipboard, + ) -> Command<Message> { match message { Message::Tick(instant) => { self.state.update(instant); @@ -117,15 +121,13 @@ impl State { ( Point::new( rng.gen_range( - -(width as f32) / 2.0, - width as f32 / 2.0, + (-(width as f32) / 2.0)..(width as f32 / 2.0), ), rng.gen_range( - -(height as f32) / 2.0, - height as f32 / 2.0, + (-(height as f32) / 2.0)..(height as f32 / 2.0), ), ), - rng.gen_range(0.5, 1.0), + rng.gen_range(0.5..1.0), ) }) .collect() diff --git a/examples/stopwatch/Cargo.toml b/examples/stopwatch/Cargo.toml index 075aa111..9f935951 100644 --- a/examples/stopwatch/Cargo.toml +++ b/examples/stopwatch/Cargo.toml @@ -6,4 +6,4 @@ edition = "2018" publish = false [dependencies] -iced = { path = "../..", features = ["tokio"] } +iced = { path = "../..", features = ["smol"] } diff --git a/examples/stopwatch/src/main.rs b/examples/stopwatch/src/main.rs index 983cf3e6..51972e01 100644 --- a/examples/stopwatch/src/main.rs +++ b/examples/stopwatch/src/main.rs @@ -1,6 +1,6 @@ use iced::{ - button, executor, time, Align, Application, Button, Column, Command, - Container, Element, HorizontalAlignment, Length, Row, Settings, + button, executor, time, Align, Application, Button, Clipboard, Column, + Command, Container, Element, HorizontalAlignment, Length, Row, Settings, Subscription, Text, }; use std::time::{Duration, Instant}; @@ -49,7 +49,11 @@ impl Application for Stopwatch { String::from("Stopwatch - Iced") } - fn update(&mut self, message: Message) -> Command<Message> { + fn update( + &mut self, + message: Message, + _clipboard: &mut Clipboard, + ) -> Command<Message> { match message { Message::Toggle => match self.state { State::Idle => { diff --git a/examples/styling/src/main.rs b/examples/styling/src/main.rs index 8975fd9a..7bc49281 100644 --- a/examples/styling/src/main.rs +++ b/examples/styling/src/main.rs @@ -1,7 +1,7 @@ use iced::{ button, scrollable, slider, text_input, Align, Button, Checkbox, Column, Container, Element, Length, ProgressBar, Radio, Row, Rule, Sandbox, - Scrollable, Settings, Slider, Space, Text, TextInput, + Scrollable, Settings, Slider, Space, Text, TextInput, Toggler, }; pub fn main() -> iced::Result { @@ -17,7 +17,8 @@ struct Styling { button: button::State, slider: slider::State, slider_value: f32, - toggle_value: bool, + checkbox_value: bool, + toggler_value: bool, } #[derive(Debug, Clone)] @@ -27,6 +28,7 @@ enum Message { ButtonPressed, SliderChanged(f32), CheckboxToggled(bool), + TogglerToggled(bool), } impl Sandbox for Styling { @@ -44,9 +46,10 @@ impl Sandbox for Styling { match message { Message::ThemeChanged(theme) => self.theme = theme, Message::InputChanged(value) => self.input_value = value, - Message::ButtonPressed => (), + Message::ButtonPressed => {} Message::SliderChanged(value) => self.slider_value = value, - Message::CheckboxToggled(value) => self.toggle_value = value, + Message::CheckboxToggled(value) => self.checkbox_value = value, + Message::TogglerToggled(value) => self.toggler_value = value, } } @@ -101,11 +104,19 @@ impl Sandbox for Styling { .push(Text::new("You did it!")); let checkbox = Checkbox::new( - self.toggle_value, - "Toggle me!", + self.checkbox_value, + "Check me!", Message::CheckboxToggled, ) - .width(Length::Fill) + .style(self.theme); + + let toggler = Toggler::new( + self.toggler_value, + String::from("Toggle me!"), + Message::TogglerToggled, + ) + .width(Length::Shrink) + .spacing(10) .style(self.theme); let content = Column::new() @@ -124,7 +135,13 @@ impl Sandbox for Styling { .align_items(Align::Center) .push(scrollable) .push(Rule::vertical(38).style(self.theme)) - .push(checkbox), + .push( + Column::new() + .width(Length::Shrink) + .spacing(20) + .push(checkbox) + .push(toggler), + ), ); Container::new(content) @@ -140,7 +157,7 @@ impl Sandbox for Styling { mod style { use iced::{ button, checkbox, container, progress_bar, radio, rule, scrollable, - slider, text_input, + slider, text_input, toggler, }; #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -231,6 +248,15 @@ mod style { } } + impl From<Theme> for Box<dyn toggler::StyleSheet> { + fn from(theme: Theme) -> Self { + match theme { + Theme::Light => Default::default(), + Theme::Dark => dark::Toggler.into(), + } + } + } + impl From<Theme> for Box<dyn rule::StyleSheet> { fn from(theme: Theme) -> Self { match theme { @@ -269,7 +295,7 @@ mod style { mod dark { use iced::{ button, checkbox, container, progress_bar, radio, rule, scrollable, - slider, text_input, Color, + slider, text_input, toggler, Color, }; const SURFACE: Color = Color::from_rgb( @@ -520,6 +546,35 @@ mod style { } } + pub struct Toggler; + + impl toggler::StyleSheet for Toggler { + fn active(&self, is_active: bool) -> toggler::Style { + toggler::Style { + background: if is_active { ACTIVE } else { SURFACE }, + background_border: None, + foreground: if is_active { Color::WHITE } else { ACTIVE }, + foreground_border: None, + } + } + + fn hovered(&self, is_active: bool) -> toggler::Style { + toggler::Style { + background: if is_active { ACTIVE } else { SURFACE }, + background_border: None, + foreground: if is_active { + Color { + a: 0.5, + ..Color::WHITE + } + } else { + Color { a: 0.5, ..ACTIVE } + }, + foreground_border: None, + } + } + } + pub struct Rule; impl rule::StyleSheet for Rule { diff --git a/examples/todos/src/main.rs b/examples/todos/src/main.rs index ccee2703..97415475 100644 --- a/examples/todos/src/main.rs +++ b/examples/todos/src/main.rs @@ -1,7 +1,7 @@ use iced::{ button, scrollable, text_input, Align, Application, Button, Checkbox, - Column, Command, Container, Element, Font, HorizontalAlignment, Length, - Row, Scrollable, Settings, Text, TextInput, + Clipboard, Column, Command, Container, Element, Font, HorizontalAlignment, + Length, Row, Scrollable, Settings, Text, TextInput, }; use serde::{Deserialize, Serialize}; @@ -58,7 +58,11 @@ impl Application for Todos { format!("Todos{} - Iced", if dirty { "*" } else { "" }) } - fn update(&mut self, message: Message) -> Command<Message> { + fn update( + &mut self, + message: Message, + _clipboard: &mut Clipboard, + ) -> Command<Message> { match self { Todos::Loading => { match message { @@ -261,8 +265,11 @@ impl Task { self.completed = completed; } TaskMessage::Edit => { + let mut text_input = text_input::State::focused(); + text_input.select_all(); + self.state = TaskState::Editing { - text_input: text_input::State::focused(), + text_input, delete_button: button::State::new(), }; } @@ -489,7 +496,6 @@ enum LoadError { #[derive(Debug, Clone)] enum SaveError { - DirectoryError, FileError, WriteError, FormatError, @@ -538,7 +544,7 @@ impl SavedState { if let Some(dir) = path.parent() { async_std::fs::create_dir_all(dir) .await - .map_err(|_| SaveError::DirectoryError)?; + .map_err(|_| SaveError::FileError)?; } { diff --git a/examples/tooltip/Cargo.toml b/examples/tooltip/Cargo.toml new file mode 100644 index 00000000..1171de00 --- /dev/null +++ b/examples/tooltip/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "tooltip" +version = "0.1.0" +authors = ["Yusuf Bera Ertan <y.bera003.06@protonmail.com>"] +edition = "2018" +publish = false + +[dependencies] +iced = { path = "../..", features = ["debug"] } diff --git a/examples/tooltip/README.md b/examples/tooltip/README.md new file mode 100644 index 00000000..4ccf6578 --- /dev/null +++ b/examples/tooltip/README.md @@ -0,0 +1,14 @@ +## Tooltip + +A tooltip. + +It displays and positions a widget on another based on cursor position. + +The __[`main`]__ file contains all the code of the example. + +You can run it with `cargo run`: +``` +cargo run --package tooltip +``` + +[`main`]: src/main.rs diff --git a/examples/tooltip/src/main.rs b/examples/tooltip/src/main.rs new file mode 100644 index 00000000..d6c8b8e1 --- /dev/null +++ b/examples/tooltip/src/main.rs @@ -0,0 +1,138 @@ +use iced::tooltip::{self, Tooltip}; +use iced::{ + button, Button, Column, Container, Element, HorizontalAlignment, Length, + Row, Sandbox, Settings, Text, VerticalAlignment, +}; + +pub fn main() { + Example::run(Settings::default()).unwrap() +} + +#[derive(Default)] +struct Example { + top: button::State, + bottom: button::State, + right: button::State, + left: button::State, + follow_cursor: button::State, +} + +#[derive(Debug, Clone, Copy)] +struct Message; + +impl Sandbox for Example { + type Message = Message; + + fn new() -> Self { + Self::default() + } + + fn title(&self) -> String { + String::from("Tooltip - Iced") + } + + fn update(&mut self, _message: Message) {} + + fn view(&mut self) -> Element<Message> { + let top = + tooltip("Tooltip at top", &mut self.top, tooltip::Position::Top); + + let bottom = tooltip( + "Tooltip at bottom", + &mut self.bottom, + tooltip::Position::Bottom, + ); + + let left = + tooltip("Tooltip at left", &mut self.left, tooltip::Position::Left); + + let right = tooltip( + "Tooltip at right", + &mut self.right, + tooltip::Position::Right, + ); + + let fixed_tooltips = Row::with_children(vec![ + top.into(), + bottom.into(), + left.into(), + right.into(), + ]) + .width(Length::Fill) + .height(Length::Fill) + .align_items(iced::Align::Center) + .spacing(50); + + let follow_cursor = tooltip( + "Tooltip follows cursor", + &mut self.follow_cursor, + tooltip::Position::FollowCursor, + ); + + let content = Column::with_children(vec![ + Container::new(fixed_tooltips) + .width(Length::Fill) + .height(Length::Fill) + .center_x() + .center_y() + .into(), + follow_cursor.into(), + ]) + .width(Length::Fill) + .height(Length::Fill) + .spacing(50); + + Container::new(content) + .width(Length::Fill) + .height(Length::Fill) + .center_x() + .center_y() + .padding(50) + .into() + } +} + +fn tooltip<'a>( + label: &str, + button_state: &'a mut button::State, + position: tooltip::Position, +) -> Element<'a, Message> { + Tooltip::new( + Button::new( + button_state, + Text::new(label) + .size(40) + .width(Length::Fill) + .height(Length::Fill) + .horizontal_alignment(HorizontalAlignment::Center) + .vertical_alignment(VerticalAlignment::Center), + ) + .on_press(Message) + .width(Length::Fill) + .height(Length::Fill), + "Tooltip", + position, + ) + .gap(5) + .padding(10) + .style(style::Tooltip) + .into() +} + +mod style { + use iced::container; + use iced::Color; + + pub struct Tooltip; + + impl container::StyleSheet for Tooltip { + fn style(&self) -> container::Style { + container::Style { + text_color: Some(Color::from_rgb8(0xEE, 0xEE, 0xEE)), + background: Some(Color::from_rgb(0.11, 0.42, 0.87).into()), + border_radius: 12.0, + ..container::Style::default() + } + } + } +} diff --git a/examples/tour/src/main.rs b/examples/tour/src/main.rs index e8755d39..1215f83d 100644 --- a/examples/tour/src/main.rs +++ b/examples/tour/src/main.rs @@ -1,7 +1,7 @@ use iced::{ button, scrollable, slider, text_input, Button, Checkbox, Color, Column, Container, Element, HorizontalAlignment, Image, Length, Radio, Row, - Sandbox, Scrollable, Settings, Slider, Space, Text, TextInput, + Sandbox, Scrollable, Settings, Slider, Space, Text, TextInput, Toggler, }; pub fn main() -> iced::Result { @@ -135,6 +135,9 @@ impl Steps { color: Color::BLACK, }, Step::Radio { selection: None }, + Step::Toggler { + can_continue: false, + }, Step::Image { width: 300, slider: slider::State::new(), @@ -206,6 +209,9 @@ enum Step { Radio { selection: Option<Language>, }, + Toggler { + can_continue: bool, + }, Image { width: u16, slider: slider::State, @@ -232,6 +238,7 @@ pub enum StepMessage { InputChanged(String), ToggleSecureInput(bool), DebugToggled(bool), + TogglerChanged(bool), } impl<'a> Step { @@ -287,6 +294,11 @@ impl<'a> Step { *is_secure = toggle; } } + StepMessage::TogglerChanged(value) => { + if let Step::Toggler { can_continue, .. } = self { + *can_continue = value; + } + } }; } @@ -294,6 +306,7 @@ impl<'a> Step { match self { Step::Welcome => "Welcome", Step::Radio { .. } => "Radio button", + Step::Toggler { .. } => "Toggler", Step::Slider { .. } => "Slider", Step::Text { .. } => "Text", Step::Image { .. } => "Image", @@ -309,6 +322,7 @@ impl<'a> Step { match self { Step::Welcome => true, Step::Radio { selection } => *selection == Some(Language::Rust), + Step::Toggler { can_continue } => *can_continue, Step::Slider { .. } => true, Step::Text { .. } => true, Step::Image { .. } => true, @@ -324,6 +338,7 @@ impl<'a> Step { match self { Step::Welcome => Self::welcome(), Step::Radio { selection } => Self::radio(*selection), + Step::Toggler { can_continue } => Self::toggler(*can_continue), Step::Slider { state, value } => Self::slider(state, *value), Step::Text { size_slider, @@ -545,6 +560,21 @@ impl<'a> Step { )) } + fn toggler(can_continue: bool) -> Column<'a, StepMessage> { + Self::container("Toggler") + .push(Text::new( + "A toggler is mostly used to enable or disable something.", + )) + .push( + Container::new(Toggler::new( + can_continue, + String::from("Toggle me to continue..."), + StepMessage::TogglerChanged, + )) + .padding([0, 40]), + ) + } + fn image( width: u16, slider: &'a mut slider::State, diff --git a/examples/url_handler/Cargo.toml b/examples/url_handler/Cargo.toml new file mode 100644 index 00000000..911b2f25 --- /dev/null +++ b/examples/url_handler/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "url_handler" +version = "0.1.0" +authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"] +edition = "2018" +publish = false + +[dependencies] +iced = { path = "../.." } +iced_native = { path = "../../native" }
\ No newline at end of file diff --git a/examples/url_handler/src/main.rs b/examples/url_handler/src/main.rs new file mode 100644 index 00000000..f14e5227 --- /dev/null +++ b/examples/url_handler/src/main.rs @@ -0,0 +1,73 @@ +use iced::{ + executor, Application, Clipboard, Command, Container, Element, Length, + Settings, Subscription, Text, +}; +use iced_native::{ + event::{MacOS, PlatformSpecific}, + Event, +}; + +pub fn main() -> iced::Result { + App::run(Settings::default()) +} + +#[derive(Debug, Default)] +struct App { + url: Option<String>, +} + +#[derive(Debug, Clone)] +enum Message { + EventOccurred(iced_native::Event), +} + +impl Application for App { + type Executor = executor::Default; + type Message = Message; + type Flags = (); + + fn new(_flags: ()) -> (App, Command<Message>) { + (App::default(), Command::none()) + } + + fn title(&self) -> String { + String::from("Url - Iced") + } + + fn update( + &mut self, + message: Message, + _clipboard: &mut Clipboard, + ) -> Command<Message> { + match message { + Message::EventOccurred(event) => { + if let Event::PlatformSpecific(PlatformSpecific::MacOS( + MacOS::ReceivedUrl(url), + )) = event + { + self.url = Some(url); + } + } + }; + + Command::none() + } + + fn subscription(&self) -> Subscription<Message> { + iced_native::subscription::events().map(Message::EventOccurred) + } + + fn view(&mut self) -> Element<Message> { + let content = match &self.url { + Some(url) => Text::new(format!("{}", url)), + None => Text::new("No URL received yet!"), + }; + + Container::new(content.size(48)) + .width(Length::Fill) + .height(Length::Fill) + .center_x() + .center_y() + .into() + } +} |