diff options
Diffstat (limited to 'examples')
29 files changed, 1087 insertions, 141 deletions
diff --git a/examples/README.md b/examples/README.md index 10c28cf5..1b9f4b56 100644 --- a/examples/README.md +++ b/examples/README.md @@ -98,7 +98,8 @@ A bunch of simpler examples exist: - [`download_progress`](download_progress), a basic application that asynchronously downloads a dummy file of 100 MB and tracks the download progress. - [`events`](events), a log of native events displayed using a conditional `Subscription`. - [`geometry`](geometry), a custom widget showcasing how to draw geometry with the `Mesh2D` primitive in [`iced_wgpu`](../wgpu). -- [`integration`](integration), a demonstration of how to integrate Iced in an existing graphical application. +- [`integration_opengl`](integration_opengl), a demonstration of how to integrate Iced in an existing OpenGL application. +- [`integration_wgpu`](integration_wgpu), a demonstration of how to integrate Iced in an existing [`wgpu`] application. - [`pane_grid`](pane_grid), a grid of panes that can be split, resized, and reorganized. - [`pick_list`](pick_list), a dropdown list of selectable options. - [`pokedex`](pokedex), an application that displays a random Pokédex entry (sprite included!) by using the [PokéAPI]. @@ -116,6 +117,7 @@ cargo run --package <example> [`lyon`]: https://github.com/nical/lyon [PokéAPI]: https://pokeapi.co/ [Ghostscript Tiger]: https://commons.wikimedia.org/wiki/File:Ghostscript_Tiger.svg +[`wgpu`]: https://github.com/gfx-rs/wgpu ## [Coffee] Since [Iced was born in May 2019], it has been powering the user interfaces in diff --git a/examples/events/src/main.rs b/examples/events/src/main.rs index 18e6364b..446c190b 100644 --- a/examples/events/src/main.rs +++ b/examples/events/src/main.rs @@ -1,22 +1,30 @@ use iced::{ - executor, Align, Application, Checkbox, Clipboard, 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 { @@ -38,27 +46,35 @@ impl Application for Events { _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> { @@ -75,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/src/main.rs b/examples/game_of_life/src/main.rs index 64599163..c3e16e8b 100644 --- a/examples/game_of_life/src/main.rs +++ b/examples/game_of_life/src/main.rs @@ -6,9 +6,11 @@ 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, Clipboard, Column, Command, Container, Element, Length, Row, Settings, Subscription, Text, @@ -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() }) } @@ -128,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_opengl/Cargo.toml b/examples/integration_opengl/Cargo.toml new file mode 100644 index 00000000..0917f2ec --- /dev/null +++ b/examples/integration_opengl/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "integration_opengl" +version = "0.1.0" +authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"] +edition = "2018" +publish = false + +[dependencies] +iced_glutin = { path = "../../glutin" } +iced_glow = { path = "../../glow" } +iced_winit = { path = "../../winit" } +env_logger = "0.8" +glow = "0.6" diff --git a/examples/integration_opengl/README.md b/examples/integration_opengl/README.md new file mode 100644 index 00000000..08cce7a6 --- /dev/null +++ b/examples/integration_opengl/README.md @@ -0,0 +1,16 @@ +## OpenGL integration + +A demonstration of how to integrate Iced in an existing graphical OpenGL application. + +The __[`main`]__ file contains all the code of the example. + +<div align="center"> + <a href="https://imgbox.com/9P9ETcod" target="_blank"><img src="https://images2.imgbox.com/2a/51/9P9ETcod_o.gif" alt="image host"/></a> +</div> + +You can run it with `cargo run`: +``` +cargo run --package integration_gl +``` + +[`main`]: src/main.rs diff --git a/examples/integration_opengl/src/controls.rs b/examples/integration_opengl/src/controls.rs new file mode 100644 index 00000000..13b7fbc2 --- /dev/null +++ b/examples/integration_opengl/src/controls.rs @@ -0,0 +1,110 @@ +use iced_glow::Renderer; +use iced_glutin::{ + slider, Align, Clipboard, Color, Column, Command, Element, Length, Program, + Row, Slider, Text, +}; + +pub struct Controls { + background_color: Color, + sliders: [slider::State; 3], +} + +#[derive(Debug, Clone)] +pub enum Message { + BackgroundColorChanged(Color), +} + +impl Controls { + pub fn new() -> Controls { + Controls { + background_color: Color::BLACK, + sliders: Default::default(), + } + } + + pub fn background_color(&self) -> Color { + self.background_color + } +} + +impl Program for Controls { + type Renderer = Renderer; + type Message = Message; + type Clipboard = Clipboard; + + fn update( + &mut self, + message: Message, + _clipboard: &mut Clipboard, + ) -> Command<Message> { + match message { + Message::BackgroundColorChanged(color) => { + self.background_color = color; + } + } + + Command::none() + } + + fn view(&mut self) -> Element<Message, Renderer> { + let [r, g, b] = &mut self.sliders; + let background_color = self.background_color; + + let sliders = Row::new() + .width(Length::Units(500)) + .spacing(20) + .push( + Slider::new(r, 0.0..=1.0, background_color.r, move |r| { + Message::BackgroundColorChanged(Color { + r, + ..background_color + }) + }) + .step(0.01), + ) + .push( + Slider::new(g, 0.0..=1.0, background_color.g, move |g| { + Message::BackgroundColorChanged(Color { + g, + ..background_color + }) + }) + .step(0.01), + ) + .push( + Slider::new(b, 0.0..=1.0, background_color.b, move |b| { + Message::BackgroundColorChanged(Color { + b, + ..background_color + }) + }) + .step(0.01), + ); + + Row::new() + .width(Length::Fill) + .height(Length::Fill) + .align_items(Align::End) + .push( + Column::new() + .width(Length::Fill) + .align_items(Align::End) + .push( + Column::new() + .padding(10) + .spacing(10) + .push( + Text::new("Background color") + .color(Color::WHITE), + ) + .push(sliders) + .push( + Text::new(format!("{:?}", background_color)) + .size(14) + .color(Color::WHITE), + ), + ), + ) + .into() + } +} diff --git a/examples/integration_opengl/src/main.rs b/examples/integration_opengl/src/main.rs new file mode 100644 index 00000000..f80915d2 --- /dev/null +++ b/examples/integration_opengl/src/main.rs @@ -0,0 +1,181 @@ +mod controls; +mod scene; + +use controls::Controls; +use scene::Scene; + +use glow; +use glow::*; +use iced_glow::{Backend, Renderer, Settings, Viewport}; +use iced_glutin::glutin; +use iced_glutin::glutin::event::{Event, WindowEvent}; +use iced_glutin::glutin::event_loop::ControlFlow; +use iced_glutin::{program, Clipboard, Debug, Size}; +use iced_winit::conversion; +use iced_winit::winit; +use winit::{dpi::PhysicalPosition, event::ModifiersState}; + +pub fn main() { + env_logger::init(); + let (gl, event_loop, windowed_context, shader_version) = { + let el = glutin::event_loop::EventLoop::new(); + + let wb = glutin::window::WindowBuilder::new() + .with_title("OpenGL integration example") + .with_inner_size(glutin::dpi::LogicalSize::new(1024.0, 768.0)); + + let windowed_context = glutin::ContextBuilder::new() + .with_vsync(true) + .build_windowed(wb, &el) + .unwrap(); + + unsafe { + let windowed_context = windowed_context.make_current().unwrap(); + + let gl = glow::Context::from_loader_function(|s| { + windowed_context.get_proc_address(s) as *const _ + }); + + // Enable auto-conversion from/to sRGB + gl.enable(glow::FRAMEBUFFER_SRGB); + + // Enable alpha blending + gl.enable(glow::BLEND); + gl.blend_func(glow::SRC_ALPHA, glow::ONE_MINUS_SRC_ALPHA); + + // Disable multisampling by default + gl.disable(glow::MULTISAMPLE); + + (gl, el, windowed_context, "#version 410") + } + }; + + let physical_size = windowed_context.window().inner_size(); + let mut viewport = Viewport::with_physical_size( + Size::new(physical_size.width, physical_size.height), + windowed_context.window().scale_factor(), + ); + + let mut cursor_position = PhysicalPosition::new(-1.0, -1.0); + let mut modifiers = ModifiersState::default(); + let mut clipboard = Clipboard::connect(&windowed_context.window()); + + let mut renderer = Renderer::new(Backend::new(&gl, Settings::default())); + + let mut debug = Debug::new(); + + let controls = Controls::new(); + let mut state = program::State::new( + controls, + viewport.logical_size(), + conversion::cursor_position(cursor_position, viewport.scale_factor()), + &mut renderer, + &mut debug, + ); + let mut resized = false; + + event_loop.run(move |event, _, control_flow| { + let scene = Scene::new(&gl, &shader_version); + *control_flow = ControlFlow::Wait; + + match event { + Event::LoopDestroyed => return, + Event::WindowEvent { event, .. } => { + match event { + WindowEvent::CursorMoved { position, .. } => { + cursor_position = position; + } + WindowEvent::ModifiersChanged(new_modifiers) => { + modifiers = new_modifiers; + } + WindowEvent::Resized(physical_size) => { + viewport = Viewport::with_physical_size( + Size::new( + physical_size.width, + physical_size.height, + ), + windowed_context.window().scale_factor(), + ); + + resized = true; + } + WindowEvent::CloseRequested => { + scene.cleanup(&gl); + *control_flow = ControlFlow::Exit + } + _ => (), + } + + // Map window event to iced event + if let Some(event) = iced_winit::conversion::window_event( + &event, + windowed_context.window().scale_factor(), + modifiers, + ) { + state.queue_event(event); + } + } + Event::MainEventsCleared => { + // If there are events pending + if !state.is_queue_empty() { + // We update iced + let _ = state.update( + viewport.logical_size(), + conversion::cursor_position( + cursor_position, + viewport.scale_factor(), + ), + &mut renderer, + &mut clipboard, + &mut debug, + ); + + // and request a redraw + windowed_context.window().request_redraw(); + } + } + Event::RedrawRequested(_) => { + if resized { + let size = windowed_context.window().inner_size(); + + unsafe { + gl.viewport( + 0, + 0, + size.width as i32, + size.height as i32, + ); + } + + resized = false; + } + + let program = state.program(); + { + // We clear the frame + scene.clear(&gl, program.background_color()); + + // Draw the scene + scene.draw(&gl); + } + + // And then iced on top + let mouse_interaction = renderer.backend_mut().draw( + &gl, + &viewport, + state.primitive(), + &debug.overlay(), + ); + // Update the mouse cursor + windowed_context.window().set_cursor_icon( + iced_winit::conversion::mouse_interaction( + mouse_interaction, + ), + ); + + windowed_context.swap_buffers().unwrap(); + } + _ => (), + } + }); +} diff --git a/examples/integration_opengl/src/scene.rs b/examples/integration_opengl/src/scene.rs new file mode 100644 index 00000000..ccca0d29 --- /dev/null +++ b/examples/integration_opengl/src/scene.rs @@ -0,0 +1,99 @@ +use glow::*; +use iced_glow::Color; + +pub struct Scene { + program: glow::Program, + vertex_array: glow::VertexArray, +} + +impl Scene { + pub fn new(gl: &glow::Context, shader_version: &str) -> Self { + unsafe { + let vertex_array = gl + .create_vertex_array() + .expect("Cannot create vertex array"); + gl.bind_vertex_array(Some(vertex_array)); + + let program = gl.create_program().expect("Cannot create program"); + + let (vertex_shader_source, fragment_shader_source) = ( + r#"const vec2 verts[3] = vec2[3]( + vec2(0.5f, 1.0f), + vec2(0.0f, 0.0f), + vec2(1.0f, 0.0f) + ); + out vec2 vert; + void main() { + vert = verts[gl_VertexID]; + gl_Position = vec4(vert - 0.5, 0.0, 1.0); + }"#, + r#"precision highp float; + in vec2 vert; + out vec4 color; + void main() { + color = vec4(vert, 0.5, 1.0); + }"#, + ); + + let shader_sources = [ + (glow::VERTEX_SHADER, vertex_shader_source), + (glow::FRAGMENT_SHADER, fragment_shader_source), + ]; + + let mut shaders = Vec::with_capacity(shader_sources.len()); + + for (shader_type, shader_source) in shader_sources.iter() { + let shader = gl + .create_shader(*shader_type) + .expect("Cannot create shader"); + gl.shader_source( + shader, + &format!("{}\n{}", shader_version, shader_source), + ); + gl.compile_shader(shader); + if !gl.get_shader_compile_status(shader) { + panic!("{}", gl.get_shader_info_log(shader)); + } + gl.attach_shader(program, shader); + shaders.push(shader); + } + + gl.link_program(program); + if !gl.get_program_link_status(program) { + panic!("{}", gl.get_program_info_log(program)); + } + + for shader in shaders { + gl.detach_shader(program, shader); + gl.delete_shader(shader); + } + + gl.use_program(Some(program)); + Self { + program, + vertex_array, + } + } + } + + pub fn clear(&self, gl: &glow::Context, background_color: Color) { + let [r, g, b, a] = background_color.into_linear(); + unsafe { + gl.clear_color(r, g, b, a); + gl.clear(glow::COLOR_BUFFER_BIT); + } + } + + pub fn draw(&self, gl: &glow::Context) { + unsafe { + gl.draw_arrays(glow::TRIANGLES, 0, 3); + } + } + + pub fn cleanup(&self, gl: &glow::Context) { + unsafe { + gl.delete_program(self.program); + gl.delete_vertex_array(self.vertex_array); + } + } +} diff --git a/examples/integration/Cargo.toml b/examples/integration_wgpu/Cargo.toml index 4515502f..a088dd1b 100644 --- a/examples/integration/Cargo.toml +++ b/examples/integration_wgpu/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "integration" +name = "integration_wgpu" version = "0.1.0" authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"] edition = "2018" @@ -7,5 +7,5 @@ publish = false [dependencies] iced_winit = { path = "../../winit" } -iced_wgpu = { path = "../../wgpu" } +iced_wgpu = { path = "../../wgpu", features = ["spirv"] } env_logger = "0.8" diff --git a/examples/integration/README.md b/examples/integration_wgpu/README.md index d5aabc19..c51c2c65 100644 --- a/examples/integration/README.md +++ b/examples/integration_wgpu/README.md @@ -1,6 +1,6 @@ -## Integration +## `wgpu` integration -A demonstration of how to integrate Iced in an existing graphical application. +A demonstration of how to integrate Iced in an existing [`wgpu`] application. The __[`main`]__ file contains all the code of the example. @@ -16,3 +16,4 @@ cargo run --package integration ``` [`main`]: src/main.rs +[`wgpu`]: https://github.com/gfx-rs/wgpu diff --git a/examples/integration/src/controls.rs b/examples/integration_wgpu/src/controls.rs index 36ee9b7e..36ee9b7e 100644 --- a/examples/integration/src/controls.rs +++ b/examples/integration_wgpu/src/controls.rs diff --git a/examples/integration/src/main.rs b/examples/integration_wgpu/src/main.rs index b57c73d8..7ef148bc 100644 --- a/examples/integration/src/main.rs +++ b/examples/integration_wgpu/src/main.rs @@ -31,10 +31,10 @@ pub fn main() { let mut clipboard = Clipboard::connect(&window); // Initialize wgpu - let instance = wgpu::Instance::new(wgpu::BackendBit::PRIMARY); + let instance = wgpu::Instance::new(wgpu::Backends::PRIMARY); let surface = unsafe { instance.create_surface(&window) }; - let (mut device, queue) = futures::executor::block_on(async { + let (format, (mut device, queue)) = futures::executor::block_on(async { let adapter = instance .request_adapter(&wgpu::RequestAdapterOptions { power_preference: wgpu::PowerPreference::HighPerformance, @@ -43,29 +43,32 @@ pub fn main() { .await .expect("Request adapter"); - adapter - .request_device( - &wgpu::DeviceDescriptor { - label: None, - features: wgpu::Features::empty(), - limits: wgpu::Limits::default(), - }, - None, - ) - .await - .expect("Request device") + ( + surface + .get_preferred_format(&adapter) + .expect("Get preferred format"), + adapter + .request_device( + &wgpu::DeviceDescriptor { + label: None, + features: wgpu::Features::empty(), + limits: wgpu::Limits::default(), + }, + None, + ) + .await + .expect("Request device"), + ) }); - let format = wgpu::TextureFormat::Bgra8UnormSrgb; - - let mut swap_chain = { + { let size = window.inner_size(); - device.create_swap_chain( - &surface, - &wgpu::SwapChainDescriptor { - usage: wgpu::TextureUsage::RENDER_ATTACHMENT, - format: format, + surface.configure( + &device, + &wgpu::SurfaceConfiguration { + usage: wgpu::TextureUsages::RENDER_ATTACHMENT, + format, width: size.width, height: size.height, present_mode: wgpu::PresentMode::Mailbox, @@ -85,7 +88,7 @@ pub fn main() { // Initialize iced let mut debug = Debug::new(); let mut renderer = - Renderer::new(Backend::new(&mut device, Settings::default())); + Renderer::new(Backend::new(&mut device, Settings::default(), format)); let mut state = program::State::new( controls, @@ -155,10 +158,10 @@ pub fn main() { if resized { let size = window.inner_size(); - swap_chain = device.create_swap_chain( - &surface, - &wgpu::SwapChainDescriptor { - usage: wgpu::TextureUsage::RENDER_ATTACHMENT, + surface.configure( + &device, + &wgpu::SurfaceConfiguration { + usage: wgpu::TextureUsages::RENDER_ATTACHMENT, format: format, width: size.width, height: size.height, @@ -169,55 +172,68 @@ pub fn main() { resized = false; } - let frame = swap_chain.get_current_frame().expect("Next frame"); + match surface.get_current_frame() { + Ok(frame) => { + let mut encoder = device.create_command_encoder( + &wgpu::CommandEncoderDescriptor { label: None }, + ); + + let program = state.program(); + + let view = frame.output.texture.create_view(&wgpu::TextureViewDescriptor::default()); + + { + // We clear the frame + let mut render_pass = scene.clear( + &view, + &mut encoder, + program.background_color(), + ); + + // Draw the scene + scene.draw(&mut render_pass); + } + + // And then iced on top + let mouse_interaction = renderer.backend_mut().draw( + &mut device, + &mut staging_belt, + &mut encoder, + &view, + &viewport, + state.primitive(), + &debug.overlay(), + ); - let mut encoder = device.create_command_encoder( - &wgpu::CommandEncoderDescriptor { label: None }, - ); + // Then we submit the work + staging_belt.finish(); + queue.submit(Some(encoder.finish())); - let program = state.program(); + // Update the mouse cursor + window.set_cursor_icon( + iced_winit::conversion::mouse_interaction( + mouse_interaction, + ), + ); - { - // We clear the frame - let mut render_pass = scene.clear( - &frame.output.view, - &mut encoder, - program.background_color(), - ); + // And recall staging buffers + local_pool + .spawner() + .spawn(staging_belt.recall()) + .expect("Recall staging buffers"); - // Draw the scene - scene.draw(&mut render_pass); + local_pool.run_until_stalled(); + } + Err(error) => match error { + wgpu::SurfaceError::OutOfMemory => { + panic!("Swapchain error: {}. Rendering cannot continue.", error) + } + _ => { + // Try rendering again next frame. + window.request_redraw(); + } + }, } - - // And then iced on top - let mouse_interaction = renderer.backend_mut().draw( - &mut device, - &mut staging_belt, - &mut encoder, - &frame.output.view, - &viewport, - state.primitive(), - &debug.overlay(), - ); - - // Then we submit the work - staging_belt.finish(); - queue.submit(Some(encoder.finish())); - - // Update the mouse cursor - window.set_cursor_icon( - iced_winit::conversion::mouse_interaction( - mouse_interaction, - ), - ); - - // And recall staging buffers - local_pool - .spawner() - .spawn(staging_belt.recall()) - .expect("Recall staging buffers"); - - local_pool.run_until_stalled(); } _ => {} } diff --git a/examples/integration/src/scene.rs b/examples/integration_wgpu/src/scene.rs index 36c0a41d..0b2b1fcd 100644 --- a/examples/integration/src/scene.rs +++ b/examples/integration_wgpu/src/scene.rs @@ -20,8 +20,8 @@ impl Scene { ) -> wgpu::RenderPass<'a> { encoder.begin_render_pass(&wgpu::RenderPassDescriptor { label: None, - color_attachments: &[wgpu::RenderPassColorAttachmentDescriptor { - attachment: target, + color_attachments: &[wgpu::RenderPassColorAttachment { + view: target, resolve_target: None, ops: wgpu::Operations { load: wgpu::LoadOp::Clear({ @@ -75,15 +75,16 @@ fn build_pipeline(device: &wgpu::Device) -> wgpu::RenderPipeline { entry_point: "main", targets: &[wgpu::ColorTargetState { format: wgpu::TextureFormat::Bgra8UnormSrgb, - color_blend: wgpu::BlendState::REPLACE, - alpha_blend: wgpu::BlendState::REPLACE, - write_mask: wgpu::ColorWrite::ALL, + blend: Some(wgpu::BlendState { + color: wgpu::BlendComponent::REPLACE, + alpha: wgpu::BlendComponent::REPLACE, + }), + write_mask: wgpu::ColorWrites::ALL, }], }), primitive: wgpu::PrimitiveState { topology: wgpu::PrimitiveTopology::TriangleList, front_face: wgpu::FrontFace::Ccw, - cull_mode: wgpu::CullMode::None, ..Default::default() }, depth_stencil: None, diff --git a/examples/integration/src/shader/frag.spv b/examples/integration_wgpu/src/shader/frag.spv Binary files differindex 9d6807c9..9d6807c9 100644 --- a/examples/integration/src/shader/frag.spv +++ b/examples/integration_wgpu/src/shader/frag.spv diff --git a/examples/integration/src/shader/vert.spv b/examples/integration_wgpu/src/shader/vert.spv Binary files differindex 0cabc9c0..0cabc9c0 100644 --- a/examples/integration/src/shader/vert.spv +++ b/examples/integration_wgpu/src/shader/vert.spv 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 4b87a568..3bd8aa25 100644 --- a/examples/pane_grid/src/main.rs +++ b/examples/pane_grid/src/main.rs @@ -11,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>, } @@ -24,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, } @@ -34,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 { @@ -60,7 +61,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 { @@ -74,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 { @@ -106,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); @@ -113,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); + } + } } } } @@ -133,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, } }) @@ -143,12 +156,20 @@ 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 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(content.id.to_string()) + Text::new(pane.content.id.to_string()) .color(if is_focused { PANE_ID_COLOR_FOCUSED } else { @@ -159,12 +180,17 @@ impl Application for Example { .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(content.view(pane, total_panes)) - .title_bar(title_bar) - .style(style::Pane { 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) @@ -212,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, @@ -220,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 { @@ -234,6 +282,7 @@ impl Content { &mut self, pane: pane_grid::Pane, total_panes: usize, + is_pinned: bool, ) -> Element<Message> { let Content { scroll, @@ -273,7 +322,7 @@ impl Content { style::Button::Primary, )); - if total_panes > 1 { + if total_panes > 1 && !is_pinned { controls = controls.push(button( close, "Close", @@ -297,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( @@ -359,6 +433,8 @@ mod style { pub enum Button { Primary, Destructive, + Control, + Pin, } impl button::StyleSheet for Button { @@ -368,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 { @@ -388,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 d2f1bb62..da1d5d5d 100644 --- a/examples/pokedex/src/main.rs +++ b/examples/pokedex/src/main.rs @@ -213,7 +213,10 @@ impl Pokemon { } 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/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 7186b950..97415475 100644 --- a/examples/todos/src/main.rs +++ b/examples/todos/src/main.rs @@ -265,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(), }; } 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() + } +} |