Merge remote-tracking branch 'origin/master' into feat/multi-window-support
# Conflicts: # Cargo.toml # core/src/window/icon.rs # core/src/window/id.rs # core/src/window/position.rs # core/src/window/settings.rs # examples/integration/src/main.rs # examples/integration_opengl/src/main.rs # glutin/src/application.rs # native/src/subscription.rs # native/src/window.rs # runtime/src/window/action.rs # src/lib.rs # src/window.rs # winit/Cargo.toml # winit/src/application.rs # winit/src/icon.rs # winit/src/settings.rs # winit/src/window.rs
This commit is contained in:
commit
633f405f3f
394 changed files with 17278 additions and 13290 deletions
|
|
@ -93,8 +93,7 @@ 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_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.
|
||||
- [`integration`](integration), 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].
|
||||
|
|
|
|||
|
|
@ -1,12 +1,13 @@
|
|||
use std::{f32::consts::PI, time::Instant};
|
||||
|
||||
use iced::executor;
|
||||
use iced::mouse;
|
||||
use iced::widget::canvas::{
|
||||
self, stroke, Cache, Canvas, Cursor, Geometry, Path, Stroke,
|
||||
self, stroke, Cache, Canvas, Geometry, Path, Stroke,
|
||||
};
|
||||
use iced::{
|
||||
Application, Command, Element, Length, Point, Rectangle, Settings,
|
||||
Subscription, Theme,
|
||||
Application, Command, Element, Length, Point, Rectangle, Renderer,
|
||||
Settings, Subscription, Theme,
|
||||
};
|
||||
|
||||
pub fn main() -> iced::Result {
|
||||
|
|
@ -75,11 +76,12 @@ impl<Message> canvas::Program<Message> for Arc {
|
|||
fn draw(
|
||||
&self,
|
||||
_state: &Self::State,
|
||||
renderer: &Renderer,
|
||||
theme: &Theme,
|
||||
bounds: Rectangle,
|
||||
_cursor: Cursor,
|
||||
_cursor: mouse::Cursor,
|
||||
) -> Vec<Geometry> {
|
||||
let geometry = self.cache.draw(bounds.size(), |frame| {
|
||||
let geometry = self.cache.draw(renderer, bounds.size(), |frame| {
|
||||
let palette = theme.palette();
|
||||
|
||||
let center = frame.center();
|
||||
|
|
|
|||
|
|
@ -61,10 +61,8 @@ impl Sandbox for Example {
|
|||
mod bezier {
|
||||
use iced::mouse;
|
||||
use iced::widget::canvas::event::{self, Event};
|
||||
use iced::widget::canvas::{
|
||||
self, Canvas, Cursor, Frame, Geometry, Path, Stroke,
|
||||
};
|
||||
use iced::{Element, Length, Point, Rectangle, Theme};
|
||||
use iced::widget::canvas::{self, Canvas, Frame, Geometry, Path, Stroke};
|
||||
use iced::{Element, Length, Point, Rectangle, Renderer, Theme};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct State {
|
||||
|
|
@ -100,10 +98,10 @@ mod bezier {
|
|||
state: &mut Self::State,
|
||||
event: Event,
|
||||
bounds: Rectangle,
|
||||
cursor: Cursor,
|
||||
cursor: mouse::Cursor,
|
||||
) -> (event::Status, Option<Curve>) {
|
||||
let cursor_position =
|
||||
if let Some(position) = cursor.position_in(&bounds) {
|
||||
if let Some(position) = cursor.position_in(bounds) {
|
||||
position
|
||||
} else {
|
||||
return (event::Status::Ignored, None);
|
||||
|
|
@ -152,22 +150,26 @@ mod bezier {
|
|||
fn draw(
|
||||
&self,
|
||||
state: &Self::State,
|
||||
renderer: &Renderer,
|
||||
_theme: &Theme,
|
||||
bounds: Rectangle,
|
||||
cursor: Cursor,
|
||||
cursor: mouse::Cursor,
|
||||
) -> Vec<Geometry> {
|
||||
let content =
|
||||
self.state.cache.draw(bounds.size(), |frame: &mut Frame| {
|
||||
let content = self.state.cache.draw(
|
||||
renderer,
|
||||
bounds.size(),
|
||||
|frame: &mut Frame| {
|
||||
Curve::draw_all(self.curves, frame);
|
||||
|
||||
frame.stroke(
|
||||
&Path::rectangle(Point::ORIGIN, frame.size()),
|
||||
Stroke::default().with_width(2.0),
|
||||
);
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
if let Some(pending) = state {
|
||||
let pending_curve = pending.draw(bounds, cursor);
|
||||
let pending_curve = pending.draw(renderer, bounds, cursor);
|
||||
|
||||
vec![content, pending_curve]
|
||||
} else {
|
||||
|
|
@ -179,9 +181,9 @@ mod bezier {
|
|||
&self,
|
||||
_state: &Self::State,
|
||||
bounds: Rectangle,
|
||||
cursor: Cursor,
|
||||
cursor: mouse::Cursor,
|
||||
) -> mouse::Interaction {
|
||||
if cursor.is_over(&bounds) {
|
||||
if cursor.is_over(bounds) {
|
||||
mouse::Interaction::Crosshair
|
||||
} else {
|
||||
mouse::Interaction::default()
|
||||
|
|
@ -216,10 +218,15 @@ mod bezier {
|
|||
}
|
||||
|
||||
impl Pending {
|
||||
fn draw(&self, bounds: Rectangle, cursor: Cursor) -> Geometry {
|
||||
let mut frame = Frame::new(bounds.size());
|
||||
fn draw(
|
||||
&self,
|
||||
renderer: &Renderer,
|
||||
bounds: Rectangle,
|
||||
cursor: mouse::Cursor,
|
||||
) -> Geometry {
|
||||
let mut frame = Frame::new(renderer, bounds.size());
|
||||
|
||||
if let Some(cursor_position) = cursor.position_in(&bounds) {
|
||||
if let Some(cursor_position) = cursor.position_in(bounds) {
|
||||
match *self {
|
||||
Pending::One { from } => {
|
||||
let line = Path::line(from, cursor_position);
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -1,10 +1,9 @@
|
|||
use iced::widget::{checkbox, column, container};
|
||||
use iced::{Element, Font, Length, Sandbox, Settings};
|
||||
use iced::executor;
|
||||
use iced::font::{self, Font};
|
||||
use iced::widget::{checkbox, column, container, text};
|
||||
use iced::{Application, Command, Element, Length, Settings, Theme};
|
||||
|
||||
const ICON_FONT: Font = Font::External {
|
||||
name: "Icons",
|
||||
bytes: include_bytes!("../fonts/icons.ttf"),
|
||||
};
|
||||
const ICON_FONT: Font = Font::with_name("icons");
|
||||
|
||||
pub fn main() -> iced::Result {
|
||||
Example::run(Settings::default())
|
||||
|
|
@ -20,24 +19,35 @@ struct Example {
|
|||
enum Message {
|
||||
DefaultChecked(bool),
|
||||
CustomChecked(bool),
|
||||
FontLoaded(Result<(), font::Error>),
|
||||
}
|
||||
|
||||
impl Sandbox for Example {
|
||||
impl Application for Example {
|
||||
type Message = Message;
|
||||
type Flags = ();
|
||||
type Executor = executor::Default;
|
||||
type Theme = Theme;
|
||||
|
||||
fn new() -> Self {
|
||||
Default::default()
|
||||
fn new(_flags: Self::Flags) -> (Self, Command<Message>) {
|
||||
(
|
||||
Self::default(),
|
||||
font::load(include_bytes!("../fonts/icons.ttf").as_slice())
|
||||
.map(Message::FontLoaded),
|
||||
)
|
||||
}
|
||||
|
||||
fn title(&self) -> String {
|
||||
String::from("Checkbox - Iced")
|
||||
}
|
||||
|
||||
fn update(&mut self, message: Message) {
|
||||
fn update(&mut self, message: Message) -> Command<Message> {
|
||||
match message {
|
||||
Message::DefaultChecked(value) => self.default_checkbox = value,
|
||||
Message::CustomChecked(value) => self.custom_checkbox = value,
|
||||
Message::FontLoaded(_) => (),
|
||||
}
|
||||
|
||||
Command::none()
|
||||
}
|
||||
|
||||
fn view(&self) -> Element<Message> {
|
||||
|
|
@ -49,6 +59,8 @@ impl Sandbox for Example {
|
|||
font: ICON_FONT,
|
||||
code_point: '\u{e901}',
|
||||
size: None,
|
||||
line_height: text::LineHeight::Relative(1.0),
|
||||
shaping: text::Shaping::Basic,
|
||||
});
|
||||
|
||||
let content = column![default_checkbox, custom_checkbox].spacing(22);
|
||||
|
|
|
|||
|
|
@ -1,11 +1,10 @@
|
|||
use iced::executor;
|
||||
use iced::widget::canvas::{
|
||||
stroke, Cache, Cursor, Geometry, LineCap, Path, Stroke,
|
||||
};
|
||||
use iced::mouse;
|
||||
use iced::widget::canvas::{stroke, Cache, Geometry, LineCap, Path, Stroke};
|
||||
use iced::widget::{canvas, container};
|
||||
use iced::{
|
||||
Application, Color, Command, Element, Length, Point, Rectangle, Settings,
|
||||
Subscription, Theme, Vector,
|
||||
Application, Color, Command, Element, Length, Point, Rectangle, Renderer,
|
||||
Settings, Subscription, Theme, Vector,
|
||||
};
|
||||
|
||||
pub fn main() -> iced::Result {
|
||||
|
|
@ -83,17 +82,18 @@ impl Application for Clock {
|
|||
}
|
||||
}
|
||||
|
||||
impl<Message> canvas::Program<Message> for Clock {
|
||||
impl<Message> canvas::Program<Message, Renderer> for Clock {
|
||||
type State = ();
|
||||
|
||||
fn draw(
|
||||
&self,
|
||||
_state: &Self::State,
|
||||
renderer: &Renderer,
|
||||
_theme: &Theme,
|
||||
bounds: Rectangle,
|
||||
_cursor: Cursor,
|
||||
_cursor: mouse::Cursor,
|
||||
) -> Vec<Geometry> {
|
||||
let clock = self.clock.draw(bounds.size(), |frame| {
|
||||
let clock = self.clock.draw(renderer, bounds.size(), |frame| {
|
||||
let center = frame.center();
|
||||
let radius = frame.width().min(frame.height()) / 2.0;
|
||||
|
||||
|
|
|
|||
|
|
@ -7,4 +7,4 @@ publish = false
|
|||
|
||||
[dependencies]
|
||||
iced = { path = "../..", features = ["canvas", "palette"] }
|
||||
palette = "0.6.0"
|
||||
palette = "0.7.0"
|
||||
|
|
|
|||
|
|
@ -1,10 +1,14 @@
|
|||
use iced::widget::canvas::{self, Canvas, Cursor, Frame, Geometry, Path};
|
||||
use iced::alignment::{self, Alignment};
|
||||
use iced::mouse;
|
||||
use iced::widget::canvas::{self, Canvas, Frame, Geometry, Path};
|
||||
use iced::widget::{column, row, text, Slider};
|
||||
use iced::{
|
||||
alignment, Alignment, Color, Element, Length, Point, Rectangle, Sandbox,
|
||||
Settings, Size, Vector,
|
||||
Color, Element, Length, Point, Rectangle, Renderer, Sandbox, Settings,
|
||||
Size, Vector,
|
||||
};
|
||||
use palette::{
|
||||
self, convert::FromColor, rgb::Rgb, Darken, Hsl, Lighten, ShiftHue,
|
||||
};
|
||||
use palette::{self, convert::FromColor, Hsl, Srgb};
|
||||
use std::marker::PhantomData;
|
||||
use std::ops::RangeInclusive;
|
||||
|
||||
|
|
@ -49,12 +53,12 @@ impl Sandbox for ColorPalette {
|
|||
|
||||
fn update(&mut self, message: Message) {
|
||||
let srgb = match message {
|
||||
Message::RgbColorChanged(rgb) => palette::Srgb::from(rgb),
|
||||
Message::HslColorChanged(hsl) => palette::Srgb::from_color(hsl),
|
||||
Message::HsvColorChanged(hsv) => palette::Srgb::from_color(hsv),
|
||||
Message::HwbColorChanged(hwb) => palette::Srgb::from_color(hwb),
|
||||
Message::LabColorChanged(lab) => palette::Srgb::from_color(lab),
|
||||
Message::LchColorChanged(lch) => palette::Srgb::from_color(lch),
|
||||
Message::RgbColorChanged(rgb) => Rgb::from(rgb),
|
||||
Message::HslColorChanged(hsl) => Rgb::from_color(hsl),
|
||||
Message::HsvColorChanged(hsv) => Rgb::from_color(hsv),
|
||||
Message::HwbColorChanged(hwb) => Rgb::from_color(hwb),
|
||||
Message::LabColorChanged(lab) => Rgb::from_color(lab),
|
||||
Message::LchColorChanged(lch) => Rgb::from_color(lch),
|
||||
};
|
||||
|
||||
self.theme = Theme::new(srgb);
|
||||
|
|
@ -63,7 +67,7 @@ impl Sandbox for ColorPalette {
|
|||
fn view(&self) -> Element<Message> {
|
||||
let base = self.theme.base;
|
||||
|
||||
let srgb = palette::Srgb::from(base);
|
||||
let srgb = Rgb::from(base);
|
||||
let hsl = palette::Hsl::from_color(srgb);
|
||||
let hsv = palette::Hsv::from_color(srgb);
|
||||
let hwb = palette::Hwb::from_color(srgb);
|
||||
|
|
@ -95,12 +99,10 @@ struct Theme {
|
|||
|
||||
impl Theme {
|
||||
pub fn new(base: impl Into<Color>) -> Theme {
|
||||
use palette::{Hue, Shade};
|
||||
|
||||
let base = base.into();
|
||||
|
||||
// Convert to HSL color for manipulation
|
||||
let hsl = Hsl::from_color(Srgb::from(base));
|
||||
let hsl = Hsl::from_color(Rgb::from(base));
|
||||
|
||||
let lower = [
|
||||
hsl.shift_hue(-135.0).lighten(0.075),
|
||||
|
|
@ -119,12 +121,12 @@ impl Theme {
|
|||
Theme {
|
||||
lower: lower
|
||||
.iter()
|
||||
.map(|&color| Srgb::from_color(color).into())
|
||||
.map(|&color| Rgb::from_color(color).into())
|
||||
.collect(),
|
||||
base,
|
||||
higher: higher
|
||||
.iter()
|
||||
.map(|&color| Srgb::from_color(color).into())
|
||||
.map(|&color| Rgb::from_color(color).into())
|
||||
.collect(),
|
||||
canvas_cache: canvas::Cache::default(),
|
||||
}
|
||||
|
|
@ -209,14 +211,14 @@ impl Theme {
|
|||
|
||||
text.vertical_alignment = alignment::Vertical::Bottom;
|
||||
|
||||
let hsl = Hsl::from_color(Srgb::from(self.base));
|
||||
let hsl = Hsl::from_color(Rgb::from(self.base));
|
||||
for i in 0..self.len() {
|
||||
let pct = (i as f32 + 1.0) / (self.len() as f32 + 1.0);
|
||||
let graded = Hsl {
|
||||
lightness: 1.0 - pct,
|
||||
..hsl
|
||||
};
|
||||
let color: Color = Srgb::from_color(graded).into();
|
||||
let color: Color = Rgb::from_color(graded).into();
|
||||
|
||||
let anchor = Point {
|
||||
x: (i as f32) * box_size.width,
|
||||
|
|
@ -243,11 +245,12 @@ impl<Message> canvas::Program<Message> for Theme {
|
|||
fn draw(
|
||||
&self,
|
||||
_state: &Self::State,
|
||||
renderer: &Renderer,
|
||||
_theme: &iced::Theme,
|
||||
bounds: Rectangle,
|
||||
_cursor: Cursor,
|
||||
_cursor: mouse::Cursor,
|
||||
) -> Vec<Geometry> {
|
||||
let theme = self.canvas_cache.draw(bounds.size(), |frame| {
|
||||
let theme = self.canvas_cache.draw(renderer, bounds.size(), |frame| {
|
||||
self.draw(frame);
|
||||
});
|
||||
|
||||
|
|
@ -351,7 +354,7 @@ impl ColorSpace for palette::Hsl {
|
|||
|
||||
fn components(&self) -> [f32; 3] {
|
||||
[
|
||||
self.hue.to_positive_degrees(),
|
||||
self.hue.into_positive_degrees(),
|
||||
self.saturation,
|
||||
self.lightness,
|
||||
]
|
||||
|
|
@ -360,7 +363,7 @@ impl ColorSpace for palette::Hsl {
|
|||
fn to_string(&self) -> String {
|
||||
format!(
|
||||
"hsl({:.1}, {:.1}%, {:.1}%)",
|
||||
self.hue.to_positive_degrees(),
|
||||
self.hue.into_positive_degrees(),
|
||||
100.0 * self.saturation,
|
||||
100.0 * self.lightness
|
||||
)
|
||||
|
|
@ -377,13 +380,17 @@ impl ColorSpace for palette::Hsv {
|
|||
}
|
||||
|
||||
fn components(&self) -> [f32; 3] {
|
||||
[self.hue.to_positive_degrees(), self.saturation, self.value]
|
||||
[
|
||||
self.hue.into_positive_degrees(),
|
||||
self.saturation,
|
||||
self.value,
|
||||
]
|
||||
}
|
||||
|
||||
fn to_string(&self) -> String {
|
||||
format!(
|
||||
"hsv({:.1}, {:.1}%, {:.1}%)",
|
||||
self.hue.to_positive_degrees(),
|
||||
self.hue.into_positive_degrees(),
|
||||
100.0 * self.saturation,
|
||||
100.0 * self.value
|
||||
)
|
||||
|
|
@ -405,7 +412,7 @@ impl ColorSpace for palette::Hwb {
|
|||
|
||||
fn components(&self) -> [f32; 3] {
|
||||
[
|
||||
self.hue.to_positive_degrees(),
|
||||
self.hue.into_positive_degrees(),
|
||||
self.whiteness,
|
||||
self.blackness,
|
||||
]
|
||||
|
|
@ -414,7 +421,7 @@ impl ColorSpace for palette::Hwb {
|
|||
fn to_string(&self) -> String {
|
||||
format!(
|
||||
"hwb({:.1}, {:.1}%, {:.1}%)",
|
||||
self.hue.to_positive_degrees(),
|
||||
self.hue.into_positive_degrees(),
|
||||
100.0 * self.whiteness,
|
||||
100.0 * self.blackness
|
||||
)
|
||||
|
|
@ -449,7 +456,7 @@ impl ColorSpace for palette::Lch {
|
|||
}
|
||||
|
||||
fn components(&self) -> [f32; 3] {
|
||||
[self.l, self.chroma, self.hue.to_positive_degrees()]
|
||||
[self.l, self.chroma, self.hue.into_positive_degrees()]
|
||||
}
|
||||
|
||||
fn to_string(&self) -> String {
|
||||
|
|
@ -457,7 +464,7 @@ impl ColorSpace for palette::Lch {
|
|||
"Lch({:.1}, {:.1}, {:.1})",
|
||||
self.l,
|
||||
self.chroma,
|
||||
self.hue.to_positive_degrees()
|
||||
self.hue.into_positive_degrees()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,4 @@ edition = "2021"
|
|||
publish = false
|
||||
|
||||
[dependencies]
|
||||
iced = { path = "../..", features = ["debug"] }
|
||||
iced_native = { path = "../../native" }
|
||||
iced_lazy = { path = "../../lazy" }
|
||||
iced = { path = "../..", features = ["debug", "lazy"] }
|
||||
|
|
|
|||
|
|
@ -47,9 +47,8 @@ impl Sandbox for Component {
|
|||
|
||||
mod numeric_input {
|
||||
use iced::alignment::{self, Alignment};
|
||||
use iced::widget::{self, button, row, text, text_input};
|
||||
use iced::{Element, Length};
|
||||
use iced_lazy::{self, Component};
|
||||
use iced::widget::{button, component, row, text, text_input, Component};
|
||||
use iced::{Element, Length, Renderer};
|
||||
|
||||
pub struct NumericInput<Message> {
|
||||
value: Option<u32>,
|
||||
|
|
@ -82,13 +81,7 @@ mod numeric_input {
|
|||
}
|
||||
}
|
||||
|
||||
impl<Message, Renderer> Component<Message, Renderer> for NumericInput<Message>
|
||||
where
|
||||
Renderer: iced_native::text::Renderer + 'static,
|
||||
Renderer::Theme: widget::button::StyleSheet
|
||||
+ widget::text_input::StyleSheet
|
||||
+ widget::text::StyleSheet,
|
||||
{
|
||||
impl<Message> Component<Message, Renderer> for NumericInput<Message> {
|
||||
type State = ();
|
||||
type Event = Event;
|
||||
|
||||
|
|
@ -141,8 +134,8 @@ mod numeric_input {
|
|||
.map(u32::to_string)
|
||||
.as_deref()
|
||||
.unwrap_or(""),
|
||||
Event::InputChanged,
|
||||
)
|
||||
.on_input(Event::InputChanged)
|
||||
.padding(10),
|
||||
button("+", Event::IncrementPressed),
|
||||
]
|
||||
|
|
@ -152,17 +145,12 @@ mod numeric_input {
|
|||
}
|
||||
}
|
||||
|
||||
impl<'a, Message, Renderer> From<NumericInput<Message>>
|
||||
for Element<'a, Message, Renderer>
|
||||
impl<'a, Message> From<NumericInput<Message>> for Element<'a, Message, Renderer>
|
||||
where
|
||||
Message: 'a,
|
||||
Renderer: 'static + iced_native::text::Renderer,
|
||||
Renderer::Theme: widget::button::StyleSheet
|
||||
+ widget::text_input::StyleSheet
|
||||
+ widget::text::StyleSheet,
|
||||
{
|
||||
fn from(numeric_input: NumericInput<Message>) -> Self {
|
||||
iced_lazy::component(numeric_input)
|
||||
component(numeric_input)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,5 +6,4 @@ edition = "2021"
|
|||
publish = false
|
||||
|
||||
[dependencies]
|
||||
iced = { path = "../.." }
|
||||
iced_native = { path = "../../native" }
|
||||
iced = { path = "../..", features = ["advanced"] }
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
//! This example showcases a drawing a quad.
|
||||
mod quad {
|
||||
use iced_native::layout::{self, Layout};
|
||||
use iced_native::renderer;
|
||||
use iced_native::widget::{self, Widget};
|
||||
use iced_native::{Color, Element, Length, Point, Rectangle, Size};
|
||||
use iced::advanced::layout::{self, Layout};
|
||||
use iced::advanced::renderer;
|
||||
use iced::advanced::widget::{self, Widget};
|
||||
use iced::mouse;
|
||||
use iced::{Color, Element, Length, Rectangle, Size};
|
||||
|
||||
pub struct CustomQuad {
|
||||
size: f32,
|
||||
|
|
@ -48,7 +49,7 @@ mod quad {
|
|||
_theme: &Renderer::Theme,
|
||||
_style: &renderer::Style,
|
||||
layout: Layout<'_>,
|
||||
_cursor_position: Point,
|
||||
_cursor: mouse::Cursor,
|
||||
_viewport: &Rectangle,
|
||||
) {
|
||||
renderer.fill_quad(
|
||||
|
|
|
|||
|
|
@ -6,5 +6,4 @@ edition = "2021"
|
|||
publish = false
|
||||
|
||||
[dependencies]
|
||||
iced = { path = "../.." }
|
||||
iced_native = { path = "../../native" }
|
||||
iced = { path = "../..", features = ["advanced"] }
|
||||
|
|
|
|||
|
|
@ -9,10 +9,11 @@ mod circle {
|
|||
// Of course, you can choose to make the implementation renderer-agnostic,
|
||||
// if you wish to, by creating your own `Renderer` trait, which could be
|
||||
// implemented by `iced_wgpu` and other renderers.
|
||||
use iced_native::layout::{self, Layout};
|
||||
use iced_native::renderer;
|
||||
use iced_native::widget::{self, Widget};
|
||||
use iced_native::{Color, Element, Length, Point, Rectangle, Size};
|
||||
use iced::advanced::layout::{self, Layout};
|
||||
use iced::advanced::renderer;
|
||||
use iced::advanced::widget::{self, Widget};
|
||||
use iced::mouse;
|
||||
use iced::{Color, Element, Length, Rectangle, Size};
|
||||
|
||||
pub struct Circle {
|
||||
radius: f32,
|
||||
|
|
@ -55,7 +56,7 @@ mod circle {
|
|||
_theme: &Renderer::Theme,
|
||||
_style: &renderer::Style,
|
||||
layout: Layout<'_>,
|
||||
_cursor_position: Point,
|
||||
_cursor: mouse::Cursor,
|
||||
_viewport: &Rectangle,
|
||||
) {
|
||||
renderer.fill_quad(
|
||||
|
|
|
|||
|
|
@ -7,8 +7,6 @@ publish = false
|
|||
|
||||
[dependencies]
|
||||
iced = { path = "../..", features = ["tokio"] }
|
||||
iced_native = { path = "../../native" }
|
||||
iced_futures = { path = "../../futures" }
|
||||
|
||||
[dependencies.reqwest]
|
||||
version = "0.11"
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use iced_native::subscription;
|
||||
use iced::subscription;
|
||||
|
||||
use std::hash::Hash;
|
||||
|
||||
|
|
@ -18,10 +18,7 @@ pub struct Download<I> {
|
|||
url: String,
|
||||
}
|
||||
|
||||
async fn download<I: Copy>(
|
||||
id: I,
|
||||
state: State,
|
||||
) -> (Option<(I, Progress)>, State) {
|
||||
async fn download<I: Copy>(id: I, state: State) -> ((I, Progress), State) {
|
||||
match state {
|
||||
State::Ready(url) => {
|
||||
let response = reqwest::get(&url).await;
|
||||
|
|
@ -30,7 +27,7 @@ async fn download<I: Copy>(
|
|||
Ok(response) => {
|
||||
if let Some(total) = response.content_length() {
|
||||
(
|
||||
Some((id, Progress::Started)),
|
||||
(id, Progress::Started),
|
||||
State::Downloading {
|
||||
response,
|
||||
total,
|
||||
|
|
@ -38,10 +35,10 @@ async fn download<I: Copy>(
|
|||
},
|
||||
)
|
||||
} else {
|
||||
(Some((id, Progress::Errored)), State::Finished)
|
||||
((id, Progress::Errored), State::Finished)
|
||||
}
|
||||
}
|
||||
Err(_) => (Some((id, Progress::Errored)), State::Finished),
|
||||
Err(_) => ((id, Progress::Errored), State::Finished),
|
||||
}
|
||||
}
|
||||
State::Downloading {
|
||||
|
|
@ -55,7 +52,7 @@ async fn download<I: Copy>(
|
|||
let percentage = (downloaded as f32 / total as f32) * 100.0;
|
||||
|
||||
(
|
||||
Some((id, Progress::Advanced(percentage))),
|
||||
(id, Progress::Advanced(percentage)),
|
||||
State::Downloading {
|
||||
response,
|
||||
total,
|
||||
|
|
@ -63,8 +60,8 @@ async fn download<I: Copy>(
|
|||
},
|
||||
)
|
||||
}
|
||||
Ok(None) => (Some((id, Progress::Finished)), State::Finished),
|
||||
Err(_) => (Some((id, Progress::Errored)), State::Finished),
|
||||
Ok(None) => ((id, Progress::Finished), State::Finished),
|
||||
Err(_) => ((id, Progress::Errored), State::Finished),
|
||||
},
|
||||
State::Finished => {
|
||||
// We do not let the stream die, as it would start a
|
||||
|
|
|
|||
|
|
@ -7,4 +7,3 @@ publish = false
|
|||
|
||||
[dependencies]
|
||||
iced = { path = "../..", features = ["debug"] }
|
||||
iced_native = { path = "../../native" }
|
||||
|
|
|
|||
|
|
@ -1,12 +1,13 @@
|
|||
use iced::alignment;
|
||||
use iced::executor;
|
||||
use iced::subscription;
|
||||
use iced::widget::{button, checkbox, container, text, Column};
|
||||
use iced::window;
|
||||
use iced::Event;
|
||||
use iced::{
|
||||
Alignment, Application, Command, Element, Length, Settings, Subscription,
|
||||
Theme,
|
||||
};
|
||||
use iced_native::Event;
|
||||
|
||||
pub fn main() -> iced::Result {
|
||||
Events::run(Settings {
|
||||
|
|
@ -17,13 +18,13 @@ pub fn main() -> iced::Result {
|
|||
|
||||
#[derive(Debug, Default)]
|
||||
struct Events {
|
||||
last: Vec<iced_native::Event>,
|
||||
last: Vec<Event>,
|
||||
enabled: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
enum Message {
|
||||
EventOccurred(iced_native::Event),
|
||||
EventOccurred(Event),
|
||||
Toggled(bool),
|
||||
Exit(window::Id),
|
||||
}
|
||||
|
|
@ -70,7 +71,7 @@ impl Application for Events {
|
|||
}
|
||||
|
||||
fn subscription(&self) -> Subscription<Message> {
|
||||
iced_native::subscription::events().map(Message::EventOccurred)
|
||||
subscription::events().map(Message::EventOccurred)
|
||||
}
|
||||
|
||||
fn view(&self) -> Element<Message> {
|
||||
|
|
|
|||
|
|
@ -145,7 +145,7 @@ impl Application for GameOfLife {
|
|||
self.grid
|
||||
.view()
|
||||
.map(move |message| Message::Grid(message, version)),
|
||||
controls
|
||||
controls,
|
||||
];
|
||||
|
||||
container(content)
|
||||
|
|
@ -204,15 +204,14 @@ fn view_controls<'a>(
|
|||
|
||||
mod grid {
|
||||
use crate::Preset;
|
||||
use iced::alignment;
|
||||
use iced::mouse;
|
||||
use iced::touch;
|
||||
use iced::widget::canvas;
|
||||
use iced::widget::canvas::event::{self, Event};
|
||||
use iced::widget::canvas::{
|
||||
Cache, Canvas, Cursor, Frame, Geometry, Path, Text,
|
||||
};
|
||||
use iced::widget::canvas::{Cache, Canvas, Frame, Geometry, Path, Text};
|
||||
use iced::{
|
||||
alignment, mouse, Color, Element, Length, Point, Rectangle, Size,
|
||||
Theme, Vector,
|
||||
Color, Element, Length, Point, Rectangle, Renderer, Size, Theme, Vector,
|
||||
};
|
||||
use rustc_hash::{FxHashMap, FxHashSet};
|
||||
use std::future::Future;
|
||||
|
|
@ -401,14 +400,14 @@ mod grid {
|
|||
interaction: &mut Interaction,
|
||||
event: Event,
|
||||
bounds: Rectangle,
|
||||
cursor: Cursor,
|
||||
cursor: mouse::Cursor,
|
||||
) -> (event::Status, Option<Message>) {
|
||||
if let Event::Mouse(mouse::Event::ButtonReleased(_)) = event {
|
||||
*interaction = Interaction::None;
|
||||
}
|
||||
|
||||
let cursor_position =
|
||||
if let Some(position) = cursor.position_in(&bounds) {
|
||||
if let Some(position) = cursor.position_in(bounds) {
|
||||
position
|
||||
} else {
|
||||
return (event::Status::Ignored, None);
|
||||
|
|
@ -536,13 +535,14 @@ mod grid {
|
|||
fn draw(
|
||||
&self,
|
||||
_interaction: &Interaction,
|
||||
renderer: &Renderer,
|
||||
_theme: &Theme,
|
||||
bounds: Rectangle,
|
||||
cursor: Cursor,
|
||||
cursor: mouse::Cursor,
|
||||
) -> Vec<Geometry> {
|
||||
let center = Vector::new(bounds.width / 2.0, bounds.height / 2.0);
|
||||
|
||||
let life = self.life_cache.draw(bounds.size(), |frame| {
|
||||
let life = self.life_cache.draw(renderer, bounds.size(), |frame| {
|
||||
let background = Path::rectangle(Point::ORIGIN, frame.size());
|
||||
frame.fill(&background, Color::from_rgb8(0x40, 0x44, 0x4B));
|
||||
|
||||
|
|
@ -565,12 +565,11 @@ mod grid {
|
|||
});
|
||||
|
||||
let overlay = {
|
||||
let mut frame = Frame::new(bounds.size());
|
||||
let mut frame = Frame::new(renderer, bounds.size());
|
||||
|
||||
let hovered_cell =
|
||||
cursor.position_in(&bounds).map(|position| {
|
||||
Cell::at(self.project(position, frame.size()))
|
||||
});
|
||||
let hovered_cell = cursor.position_in(bounds).map(|position| {
|
||||
Cell::at(self.project(position, frame.size()))
|
||||
});
|
||||
|
||||
if let Some(cell) = hovered_cell {
|
||||
frame.with_save(|frame| {
|
||||
|
|
@ -626,38 +625,40 @@ mod grid {
|
|||
if self.scaling < 0.2 || !self.show_lines {
|
||||
vec![life, overlay]
|
||||
} else {
|
||||
let grid = self.grid_cache.draw(bounds.size(), |frame| {
|
||||
frame.translate(center);
|
||||
frame.scale(self.scaling);
|
||||
frame.translate(self.translation);
|
||||
frame.scale(Cell::SIZE as f32);
|
||||
let grid =
|
||||
self.grid_cache.draw(renderer, bounds.size(), |frame| {
|
||||
frame.translate(center);
|
||||
frame.scale(self.scaling);
|
||||
frame.translate(self.translation);
|
||||
frame.scale(Cell::SIZE as f32);
|
||||
|
||||
let region = self.visible_region(frame.size());
|
||||
let rows = region.rows();
|
||||
let columns = region.columns();
|
||||
let (total_rows, total_columns) =
|
||||
(rows.clone().count(), columns.clone().count());
|
||||
let width = 2.0 / Cell::SIZE as f32;
|
||||
let color = Color::from_rgb8(70, 74, 83);
|
||||
let region = self.visible_region(frame.size());
|
||||
let rows = region.rows();
|
||||
let columns = region.columns();
|
||||
let (total_rows, total_columns) =
|
||||
(rows.clone().count(), columns.clone().count());
|
||||
let width = 2.0 / Cell::SIZE as f32;
|
||||
let color = Color::from_rgb8(70, 74, 83);
|
||||
|
||||
frame.translate(Vector::new(-width / 2.0, -width / 2.0));
|
||||
frame
|
||||
.translate(Vector::new(-width / 2.0, -width / 2.0));
|
||||
|
||||
for row in region.rows() {
|
||||
frame.fill_rectangle(
|
||||
Point::new(*columns.start() as f32, row as f32),
|
||||
Size::new(total_columns as f32, width),
|
||||
color,
|
||||
);
|
||||
}
|
||||
for row in region.rows() {
|
||||
frame.fill_rectangle(
|
||||
Point::new(*columns.start() as f32, row as f32),
|
||||
Size::new(total_columns as f32, width),
|
||||
color,
|
||||
);
|
||||
}
|
||||
|
||||
for column in region.columns() {
|
||||
frame.fill_rectangle(
|
||||
Point::new(column as f32, *rows.start() as f32),
|
||||
Size::new(width, total_rows as f32),
|
||||
color,
|
||||
);
|
||||
}
|
||||
});
|
||||
for column in region.columns() {
|
||||
frame.fill_rectangle(
|
||||
Point::new(column as f32, *rows.start() as f32),
|
||||
Size::new(width, total_rows as f32),
|
||||
color,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
vec![life, grid, overlay]
|
||||
}
|
||||
|
|
@ -667,13 +668,13 @@ mod grid {
|
|||
&self,
|
||||
interaction: &Interaction,
|
||||
bounds: Rectangle,
|
||||
cursor: Cursor,
|
||||
cursor: mouse::Cursor,
|
||||
) -> mouse::Interaction {
|
||||
match interaction {
|
||||
Interaction::Drawing => mouse::Interaction::Crosshair,
|
||||
Interaction::Erasing => mouse::Interaction::Crosshair,
|
||||
Interaction::Panning { .. } => mouse::Interaction::Grabbing,
|
||||
Interaction::None if cursor.is_over(&bounds) => {
|
||||
Interaction::None if cursor.is_over(bounds) => {
|
||||
mouse::Interaction::Crosshair
|
||||
}
|
||||
_ => mouse::Interaction::default(),
|
||||
|
|
|
|||
|
|
@ -6,6 +6,4 @@ edition = "2021"
|
|||
publish = false
|
||||
|
||||
[dependencies]
|
||||
iced = { path = "../.." }
|
||||
iced_native = { path = "../../native" }
|
||||
iced_graphics = { path = "../../graphics" }
|
||||
iced = { path = "../..", features = ["advanced"] }
|
||||
|
|
|
|||
|
|
@ -1,24 +1,12 @@
|
|||
//! This example showcases a simple native custom widget that renders using
|
||||
//! arbitrary low-level geometry.
|
||||
mod rainbow {
|
||||
// For now, to implement a custom native widget you will need to add
|
||||
// `iced_native` and `iced_wgpu` to your dependencies.
|
||||
//
|
||||
// Then, you simply need to define your widget type and implement the
|
||||
// `iced_native::Widget` trait with the `iced_wgpu::Renderer`.
|
||||
//
|
||||
// Of course, you can choose to make the implementation renderer-agnostic,
|
||||
// if you wish to, by creating your own `Renderer` trait, which could be
|
||||
// implemented by `iced_wgpu` and other renderers.
|
||||
use iced_graphics::renderer::{self, Renderer};
|
||||
use iced_graphics::triangle::ColoredVertex2D;
|
||||
use iced_graphics::{Backend, Primitive};
|
||||
|
||||
use iced_native::layout;
|
||||
use iced_native::widget::{self, Widget};
|
||||
use iced_native::{
|
||||
Element, Layout, Length, Point, Rectangle, Size, Vector,
|
||||
};
|
||||
use iced::advanced::graphics::color;
|
||||
use iced::advanced::layout::{self, Layout};
|
||||
use iced::advanced::renderer;
|
||||
use iced::advanced::widget::{self, Widget};
|
||||
use iced::mouse;
|
||||
use iced::{Element, Length, Rectangle, Renderer, Size, Theme, Vector};
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default)]
|
||||
pub struct Rainbow;
|
||||
|
|
@ -27,10 +15,7 @@ mod rainbow {
|
|||
Rainbow
|
||||
}
|
||||
|
||||
impl<Message, B, T> Widget<Message, Renderer<B, T>> for Rainbow
|
||||
where
|
||||
B: Backend,
|
||||
{
|
||||
impl<Message> Widget<Message, Renderer> for Rainbow {
|
||||
fn width(&self) -> Length {
|
||||
Length::Fill
|
||||
}
|
||||
|
|
@ -41,7 +26,7 @@ mod rainbow {
|
|||
|
||||
fn layout(
|
||||
&self,
|
||||
_renderer: &Renderer<B, T>,
|
||||
_renderer: &Renderer,
|
||||
limits: &layout::Limits,
|
||||
) -> layout::Node {
|
||||
let size = limits.width(Length::Fill).resolve(Size::ZERO);
|
||||
|
|
@ -52,17 +37,17 @@ mod rainbow {
|
|||
fn draw(
|
||||
&self,
|
||||
_tree: &widget::Tree,
|
||||
renderer: &mut Renderer<B, T>,
|
||||
_theme: &T,
|
||||
renderer: &mut Renderer,
|
||||
_theme: &Theme,
|
||||
_style: &renderer::Style,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
cursor: mouse::Cursor,
|
||||
_viewport: &Rectangle,
|
||||
) {
|
||||
use iced_graphics::triangle::Mesh2D;
|
||||
use iced_native::Renderer as _;
|
||||
use iced::advanced::graphics::mesh::{self, Mesh, SolidVertex2D};
|
||||
use iced::advanced::Renderer as _;
|
||||
|
||||
let b = layout.bounds();
|
||||
let bounds = layout.bounds();
|
||||
|
||||
// R O Y G B I V
|
||||
let color_r = [1.0, 0.0, 0.0, 1.0];
|
||||
|
|
@ -75,61 +60,61 @@ mod rainbow {
|
|||
let color_v = [0.75, 0.0, 0.5, 1.0];
|
||||
|
||||
let posn_center = {
|
||||
if b.contains(cursor_position) {
|
||||
[cursor_position.x - b.x, cursor_position.y - b.y]
|
||||
if let Some(cursor_position) = cursor.position_in(bounds) {
|
||||
[cursor_position.x, cursor_position.y]
|
||||
} else {
|
||||
[b.width / 2.0, b.height / 2.0]
|
||||
[bounds.width / 2.0, bounds.height / 2.0]
|
||||
}
|
||||
};
|
||||
|
||||
let posn_tl = [0.0, 0.0];
|
||||
let posn_t = [b.width / 2.0, 0.0];
|
||||
let posn_tr = [b.width, 0.0];
|
||||
let posn_r = [b.width, b.height / 2.0];
|
||||
let posn_br = [b.width, b.height];
|
||||
let posn_b = [(b.width / 2.0), b.height];
|
||||
let posn_bl = [0.0, b.height];
|
||||
let posn_l = [0.0, b.height / 2.0];
|
||||
let posn_t = [bounds.width / 2.0, 0.0];
|
||||
let posn_tr = [bounds.width, 0.0];
|
||||
let posn_r = [bounds.width, bounds.height / 2.0];
|
||||
let posn_br = [bounds.width, bounds.height];
|
||||
let posn_b = [(bounds.width / 2.0), bounds.height];
|
||||
let posn_bl = [0.0, bounds.height];
|
||||
let posn_l = [0.0, bounds.height / 2.0];
|
||||
|
||||
let mesh = Primitive::SolidMesh {
|
||||
size: b.size(),
|
||||
buffers: Mesh2D {
|
||||
let mesh = Mesh::Solid {
|
||||
size: bounds.size(),
|
||||
buffers: mesh::Indexed {
|
||||
vertices: vec![
|
||||
ColoredVertex2D {
|
||||
SolidVertex2D {
|
||||
position: posn_center,
|
||||
color: [1.0, 1.0, 1.0, 1.0],
|
||||
color: color::pack([1.0, 1.0, 1.0, 1.0]),
|
||||
},
|
||||
ColoredVertex2D {
|
||||
SolidVertex2D {
|
||||
position: posn_tl,
|
||||
color: color_r,
|
||||
color: color::pack(color_r),
|
||||
},
|
||||
ColoredVertex2D {
|
||||
SolidVertex2D {
|
||||
position: posn_t,
|
||||
color: color_o,
|
||||
color: color::pack(color_o),
|
||||
},
|
||||
ColoredVertex2D {
|
||||
SolidVertex2D {
|
||||
position: posn_tr,
|
||||
color: color_y,
|
||||
color: color::pack(color_y),
|
||||
},
|
||||
ColoredVertex2D {
|
||||
SolidVertex2D {
|
||||
position: posn_r,
|
||||
color: color_g,
|
||||
color: color::pack(color_g),
|
||||
},
|
||||
ColoredVertex2D {
|
||||
SolidVertex2D {
|
||||
position: posn_br,
|
||||
color: color_gb,
|
||||
color: color::pack(color_gb),
|
||||
},
|
||||
ColoredVertex2D {
|
||||
SolidVertex2D {
|
||||
position: posn_b,
|
||||
color: color_b,
|
||||
color: color::pack(color_b),
|
||||
},
|
||||
ColoredVertex2D {
|
||||
SolidVertex2D {
|
||||
position: posn_bl,
|
||||
color: color_i,
|
||||
color: color::pack(color_i),
|
||||
},
|
||||
ColoredVertex2D {
|
||||
SolidVertex2D {
|
||||
position: posn_l,
|
||||
color: color_v,
|
||||
color: color::pack(color_v),
|
||||
},
|
||||
],
|
||||
indices: vec![
|
||||
|
|
@ -145,16 +130,16 @@ mod rainbow {
|
|||
},
|
||||
};
|
||||
|
||||
renderer.with_translation(Vector::new(b.x, b.y), |renderer| {
|
||||
renderer.draw_primitive(mesh);
|
||||
});
|
||||
renderer.with_translation(
|
||||
Vector::new(bounds.x, bounds.y),
|
||||
|renderer| {
|
||||
renderer.draw_mesh(mesh);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, Message, B, T> From<Rainbow> for Element<'a, Message, Renderer<B, T>>
|
||||
where
|
||||
B: Backend,
|
||||
{
|
||||
impl<'a, Message> From<Rainbow> for Element<'a, Message, Renderer> {
|
||||
fn from(rainbow: Rainbow) -> Self {
|
||||
Self::new(rainbow)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
[package]
|
||||
name = "integration_wgpu"
|
||||
name = "integration"
|
||||
version = "0.1.0"
|
||||
authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]
|
||||
edition = "2021"
|
||||
|
|
@ -7,8 +7,10 @@ publish = false
|
|||
|
||||
[dependencies]
|
||||
iced_winit = { path = "../../winit" }
|
||||
iced_wgpu = { path = "../../wgpu", features = ["webgl"] }
|
||||
env_logger = "0.8"
|
||||
iced_wgpu = { path = "../../wgpu" }
|
||||
iced_widget = { path = "../../widget" }
|
||||
iced_renderer = { path = "../../renderer", features = ["wgpu"] }
|
||||
env_logger = "0.10"
|
||||
|
||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||
console_error_panic_hook = "0.1.7"
|
||||
|
|
@ -8,8 +8,8 @@
|
|||
<h1>integration_wgpu</h1>
|
||||
<canvas id="iced_canvas"></canvas>
|
||||
<script type="module">
|
||||
import init from "./integration_wgpu.js";
|
||||
init('./integration_wgpu_bg.wasm');
|
||||
import init from "./integration.js";
|
||||
init('./integration_bg.wasm');
|
||||
</script>
|
||||
<style>
|
||||
body {
|
||||
|
|
@ -1,6 +1,8 @@
|
|||
use iced_wgpu::Renderer;
|
||||
use iced_winit::widget::{slider, text_input, Column, Row, Text};
|
||||
use iced_winit::{Alignment, Color, Command, Element, Length, Program};
|
||||
use iced_widget::{slider, text_input, Column, Row, Text};
|
||||
use iced_winit::core::{Alignment, Color, Element, Length};
|
||||
use iced_winit::runtime::{Command, Program};
|
||||
use iced_winit::style::Theme;
|
||||
|
||||
pub struct Controls {
|
||||
background_color: Color,
|
||||
|
|
@ -27,7 +29,7 @@ impl Controls {
|
|||
}
|
||||
|
||||
impl Program for Controls {
|
||||
type Renderer = Renderer;
|
||||
type Renderer = Renderer<Theme>;
|
||||
type Message = Message;
|
||||
|
||||
fn update(&mut self, message: Message) -> Command<Message> {
|
||||
|
|
@ -43,7 +45,7 @@ impl Program for Controls {
|
|||
Command::none()
|
||||
}
|
||||
|
||||
fn view(&self) -> Element<Message, Renderer> {
|
||||
fn view(&self) -> Element<Message, Renderer<Theme>> {
|
||||
let background_color = self.background_color;
|
||||
let text = &self.text;
|
||||
|
||||
|
|
@ -100,11 +102,10 @@ impl Program for Controls {
|
|||
.size(14)
|
||||
.style(Color::WHITE),
|
||||
)
|
||||
.push(text_input(
|
||||
"Placeholder",
|
||||
text,
|
||||
Message::TextChanged,
|
||||
)),
|
||||
.push(
|
||||
text_input("Placeholder", text)
|
||||
.on_input(Message::TextChanged),
|
||||
),
|
||||
),
|
||||
)
|
||||
.into()
|
||||
|
|
@ -4,14 +4,17 @@ mod scene;
|
|||
use controls::Controls;
|
||||
use scene::Scene;
|
||||
|
||||
use iced_wgpu::{wgpu, Backend, Renderer, Settings, Viewport};
|
||||
use iced_winit::{
|
||||
conversion, futures, program, renderer, window, winit, Clipboard, Color,
|
||||
Debug, Size,
|
||||
};
|
||||
use iced_wgpu::graphics::Viewport;
|
||||
use iced_wgpu::{wgpu, Backend, Renderer, Settings};
|
||||
use iced_winit::core::mouse;
|
||||
use iced_winit::core::renderer;
|
||||
use iced_winit::core::{Color, Size};
|
||||
use iced_winit::runtime::program;
|
||||
use iced_winit::runtime::Debug;
|
||||
use iced_winit::style::Theme;
|
||||
use iced_winit::{conversion, futures, winit, Clipboard};
|
||||
|
||||
use winit::{
|
||||
dpi::PhysicalPosition,
|
||||
event::{Event, ModifiersState, WindowEvent},
|
||||
event_loop::{ControlFlow, EventLoop},
|
||||
};
|
||||
|
|
@ -23,19 +26,20 @@ use web_sys::HtmlCanvasElement;
|
|||
#[cfg(target_arch = "wasm32")]
|
||||
use winit::platform::web::WindowBuilderExtWebSys;
|
||||
|
||||
pub fn main() {
|
||||
pub fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
let canvas_element = {
|
||||
console_log::init_with_level(log::Level::Debug)
|
||||
.expect("could not initialize logger");
|
||||
console_log::init_with_level(log::Level::Debug)?;
|
||||
|
||||
std::panic::set_hook(Box::new(console_error_panic_hook::hook));
|
||||
|
||||
web_sys::window()
|
||||
.and_then(|win| win.document())
|
||||
.and_then(|doc| doc.get_element_by_id("iced_canvas"))
|
||||
.and_then(|element| element.dyn_into::<HtmlCanvasElement>().ok())
|
||||
.expect("Canvas with id `iced_canvas` is missing")
|
||||
.expect("Get canvas element")
|
||||
};
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
env_logger::init();
|
||||
|
||||
|
|
@ -45,23 +49,21 @@ pub fn main() {
|
|||
#[cfg(target_arch = "wasm32")]
|
||||
let window = winit::window::WindowBuilder::new()
|
||||
.with_canvas(Some(canvas_element))
|
||||
.build(&event_loop)
|
||||
.expect("Failed to build winit window");
|
||||
.build(&event_loop)?;
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
let window = winit::window::Window::new(&event_loop).unwrap();
|
||||
let window = winit::window::Window::new(&event_loop)?;
|
||||
|
||||
let physical_size = window.inner_size();
|
||||
let mut viewport = Viewport::with_physical_size(
|
||||
Size::new(physical_size.width, physical_size.height),
|
||||
window.scale_factor(),
|
||||
);
|
||||
let mut cursor_position = PhysicalPosition::new(-1.0, -1.0);
|
||||
let mut cursor_position = None;
|
||||
let mut modifiers = ModifiersState::default();
|
||||
let mut clipboard = Clipboard::connect(&window);
|
||||
|
||||
// Initialize wgpu
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
let default_backend = wgpu::Backends::GL;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
|
|
@ -70,46 +72,55 @@ pub fn main() {
|
|||
let backend =
|
||||
wgpu::util::backend_bits_from_env().unwrap_or(default_backend);
|
||||
|
||||
let instance = wgpu::Instance::new(backend);
|
||||
let surface = unsafe { instance.create_surface(&window) };
|
||||
|
||||
let (format, (device, queue)) = futures::executor::block_on(async {
|
||||
let adapter = wgpu::util::initialize_adapter_from_env_or_default(
|
||||
&instance,
|
||||
backend,
|
||||
Some(&surface),
|
||||
)
|
||||
.await
|
||||
.expect("No suitable GPU adapters found on the system!");
|
||||
|
||||
let adapter_features = adapter.features();
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
let needed_limits = wgpu::Limits::downlevel_webgl2_defaults()
|
||||
.using_resolution(adapter.limits());
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
let needed_limits = wgpu::Limits::default();
|
||||
|
||||
(
|
||||
surface
|
||||
.get_supported_formats(&adapter)
|
||||
.first()
|
||||
.copied()
|
||||
.expect("Get preferred format"),
|
||||
adapter
|
||||
.request_device(
|
||||
&wgpu::DeviceDescriptor {
|
||||
label: None,
|
||||
features: adapter_features & wgpu::Features::default(),
|
||||
limits: needed_limits,
|
||||
},
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.expect("Request device"),
|
||||
)
|
||||
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
|
||||
backends: backend,
|
||||
..Default::default()
|
||||
});
|
||||
let surface = unsafe { instance.create_surface(&window) }?;
|
||||
|
||||
let (format, (device, queue)) =
|
||||
futures::futures::executor::block_on(async {
|
||||
let adapter = wgpu::util::initialize_adapter_from_env_or_default(
|
||||
&instance,
|
||||
backend,
|
||||
Some(&surface),
|
||||
)
|
||||
.await
|
||||
.expect("Create adapter");
|
||||
|
||||
let adapter_features = adapter.features();
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
let needed_limits = wgpu::Limits::downlevel_webgl2_defaults()
|
||||
.using_resolution(adapter.limits());
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
let needed_limits = wgpu::Limits::default();
|
||||
|
||||
let capabilities = surface.get_capabilities(&adapter);
|
||||
|
||||
(
|
||||
capabilities
|
||||
.formats
|
||||
.iter()
|
||||
.copied()
|
||||
.find(wgpu::TextureFormat::is_srgb)
|
||||
.or_else(|| capabilities.formats.first().copied())
|
||||
.expect("Get preferred format"),
|
||||
adapter
|
||||
.request_device(
|
||||
&wgpu::DeviceDescriptor {
|
||||
label: None,
|
||||
features: adapter_features
|
||||
& wgpu::Features::default(),
|
||||
limits: needed_limits,
|
||||
},
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.expect("Request device"),
|
||||
)
|
||||
});
|
||||
|
||||
surface.configure(
|
||||
&device,
|
||||
|
|
@ -120,22 +131,24 @@ pub fn main() {
|
|||
height: physical_size.height,
|
||||
present_mode: wgpu::PresentMode::AutoVsync,
|
||||
alpha_mode: wgpu::CompositeAlphaMode::Auto,
|
||||
view_formats: vec![],
|
||||
},
|
||||
);
|
||||
|
||||
let mut resized = false;
|
||||
|
||||
// Initialize staging belt
|
||||
let mut staging_belt = wgpu::util::StagingBelt::new(5 * 1024);
|
||||
|
||||
// Initialize scene and GUI controls
|
||||
let scene = Scene::new(&device, format);
|
||||
let controls = Controls::new();
|
||||
|
||||
// Initialize iced
|
||||
let mut debug = Debug::new();
|
||||
let mut renderer =
|
||||
Renderer::new(Backend::new(&device, Settings::default(), format));
|
||||
let mut renderer = Renderer::new(Backend::new(
|
||||
&device,
|
||||
&queue,
|
||||
Settings::default(),
|
||||
format,
|
||||
));
|
||||
|
||||
let mut state = program::State::new(
|
||||
controls,
|
||||
|
|
@ -153,7 +166,7 @@ pub fn main() {
|
|||
Event::WindowEvent { event, .. } => {
|
||||
match event {
|
||||
WindowEvent::CursorMoved { position, .. } => {
|
||||
cursor_position = position;
|
||||
cursor_position = Some(position);
|
||||
}
|
||||
WindowEvent::ModifiersChanged(new_modifiers) => {
|
||||
modifiers = new_modifiers;
|
||||
|
|
@ -183,13 +196,20 @@ pub fn main() {
|
|||
// We update iced
|
||||
let _ = state.update(
|
||||
viewport.logical_size(),
|
||||
conversion::cursor_position(
|
||||
cursor_position,
|
||||
viewport.scale_factor(),
|
||||
),
|
||||
cursor_position
|
||||
.map(|p| {
|
||||
conversion::cursor_position(
|
||||
p,
|
||||
viewport.scale_factor(),
|
||||
)
|
||||
})
|
||||
.map(mouse::Cursor::Available)
|
||||
.unwrap_or(mouse::Cursor::Unavailable),
|
||||
&mut renderer,
|
||||
&iced_wgpu::Theme::Dark,
|
||||
&renderer::Style { text_color: Color::WHITE },
|
||||
&Theme::Dark,
|
||||
&renderer::Style {
|
||||
text_color: Color::WHITE,
|
||||
},
|
||||
&mut clipboard,
|
||||
&mut debug,
|
||||
);
|
||||
|
|
@ -215,7 +235,8 @@ pub fn main() {
|
|||
width: size.width,
|
||||
height: size.height,
|
||||
present_mode: wgpu::PresentMode::AutoVsync,
|
||||
alpha_mode: wgpu::CompositeAlphaMode::Auto
|
||||
alpha_mode: wgpu::CompositeAlphaMode::Auto,
|
||||
view_formats: vec![],
|
||||
},
|
||||
);
|
||||
|
||||
|
|
@ -230,7 +251,9 @@ pub fn main() {
|
|||
|
||||
let program = state.program();
|
||||
|
||||
let view = frame.texture.create_view(&wgpu::TextureViewDescriptor::default());
|
||||
let view = frame.texture.create_view(
|
||||
&wgpu::TextureViewDescriptor::default(),
|
||||
);
|
||||
|
||||
{
|
||||
// We clear the frame
|
||||
|
|
@ -248,8 +271,9 @@ pub fn main() {
|
|||
renderer.with_primitives(|backend, primitive| {
|
||||
backend.present(
|
||||
&device,
|
||||
&mut staging_belt,
|
||||
&queue,
|
||||
&mut encoder,
|
||||
None,
|
||||
&view,
|
||||
primitive,
|
||||
&viewport,
|
||||
|
|
@ -258,24 +282,22 @@ pub fn main() {
|
|||
});
|
||||
|
||||
// Then we submit the work
|
||||
staging_belt.finish();
|
||||
queue.submit(Some(encoder.finish()));
|
||||
frame.present();
|
||||
|
||||
// Update the mouse cursor
|
||||
window.set_cursor_icon(
|
||||
iced_winit::conversion::mouse_interaction(
|
||||
state.mouse_interaction(),
|
||||
),
|
||||
);
|
||||
|
||||
// And recall staging buffers
|
||||
staging_belt.recall();
|
||||
|
||||
window.set_cursor_icon(
|
||||
iced_winit::conversion::mouse_interaction(
|
||||
state.mouse_interaction(),
|
||||
),
|
||||
);
|
||||
}
|
||||
Err(error) => match error {
|
||||
wgpu::SurfaceError::OutOfMemory => {
|
||||
panic!("Swapchain error: {error}. Rendering cannot continue.")
|
||||
panic!(
|
||||
"Swapchain error: {error}. \
|
||||
Rendering cannot continue."
|
||||
)
|
||||
}
|
||||
_ => {
|
||||
// Try rendering again next frame.
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
use iced_wgpu::wgpu;
|
||||
use iced_winit::Color;
|
||||
use iced_winit::core::Color;
|
||||
|
||||
pub struct Scene {
|
||||
pipeline: wgpu::RenderPipeline,
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
[package]
|
||||
name = "integration_opengl"
|
||||
version = "0.1.0"
|
||||
authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]
|
||||
edition = "2021"
|
||||
publish = false
|
||||
|
||||
[dependencies]
|
||||
iced_glutin = { path = "../../glutin" }
|
||||
iced_glow = { path = "../../glow" }
|
||||
iced_winit = { path = "../../winit" }
|
||||
env_logger = "0.8"
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
## 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_opengl
|
||||
```
|
||||
|
||||
[`main`]: src/main.rs
|
||||
|
|
@ -1,101 +0,0 @@
|
|||
use iced_glow::Renderer;
|
||||
use iced_glutin::widget::Slider;
|
||||
use iced_glutin::widget::{Column, Row, Text};
|
||||
use iced_glutin::{Alignment, Color, Command, Element, Length, Program};
|
||||
|
||||
pub struct Controls {
|
||||
background_color: Color,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Message {
|
||||
BackgroundColorChanged(Color),
|
||||
}
|
||||
|
||||
impl Controls {
|
||||
pub fn new() -> Controls {
|
||||
Controls {
|
||||
background_color: Color::BLACK,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn background_color(&self) -> Color {
|
||||
self.background_color
|
||||
}
|
||||
}
|
||||
|
||||
impl Program for Controls {
|
||||
type Renderer = Renderer;
|
||||
type Message = Message;
|
||||
|
||||
fn update(&mut self, message: Message) -> Command<Message> {
|
||||
match message {
|
||||
Message::BackgroundColorChanged(color) => {
|
||||
self.background_color = color;
|
||||
}
|
||||
}
|
||||
|
||||
Command::none()
|
||||
}
|
||||
|
||||
fn view(&self) -> Element<Message, Renderer> {
|
||||
let background_color = self.background_color;
|
||||
|
||||
let sliders = Row::new()
|
||||
.width(500)
|
||||
.spacing(20)
|
||||
.push(
|
||||
Slider::new(0.0..=1.0, background_color.r, move |r| {
|
||||
Message::BackgroundColorChanged(Color {
|
||||
r,
|
||||
..background_color
|
||||
})
|
||||
})
|
||||
.step(0.01),
|
||||
)
|
||||
.push(
|
||||
Slider::new(0.0..=1.0, background_color.g, move |g| {
|
||||
Message::BackgroundColorChanged(Color {
|
||||
g,
|
||||
..background_color
|
||||
})
|
||||
})
|
||||
.step(0.01),
|
||||
)
|
||||
.push(
|
||||
Slider::new(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(Alignment::End)
|
||||
.push(
|
||||
Column::new()
|
||||
.width(Length::Fill)
|
||||
.align_items(Alignment::End)
|
||||
.push(
|
||||
Column::new()
|
||||
.padding(10)
|
||||
.spacing(10)
|
||||
.push(
|
||||
Text::new("Background color")
|
||||
.style(Color::WHITE),
|
||||
)
|
||||
.push(sliders)
|
||||
.push(
|
||||
Text::new(format!("{background_color:?}"))
|
||||
.size(14)
|
||||
.style(Color::WHITE),
|
||||
),
|
||||
),
|
||||
)
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
|
@ -1,188 +0,0 @@
|
|||
mod controls;
|
||||
mod scene;
|
||||
|
||||
use controls::Controls;
|
||||
use scene::Scene;
|
||||
|
||||
use glow::*;
|
||||
use glutin::dpi::PhysicalPosition;
|
||||
use glutin::event::{Event, ModifiersState, WindowEvent};
|
||||
use glutin::event_loop::ControlFlow;
|
||||
use iced_glow::glow;
|
||||
use iced_glow::{Backend, Renderer, Settings, Viewport};
|
||||
use iced_glutin::conversion;
|
||||
use iced_glutin::glutin;
|
||||
use iced_glutin::renderer;
|
||||
use iced_glutin::{program, Clipboard, Color, Debug, Size};
|
||||
|
||||
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(),
|
||||
&mut renderer,
|
||||
&mut debug,
|
||||
);
|
||||
let mut resized = false;
|
||||
|
||||
let scene = Scene::new(&gl, shader_version);
|
||||
|
||||
event_loop.run(move |event, _, control_flow| {
|
||||
*control_flow = ControlFlow::Wait;
|
||||
|
||||
match event {
|
||||
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(
|
||||
iced_winit::window::Id::MAIN,
|
||||
&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,
|
||||
&iced_glow::Theme::Dark,
|
||||
&renderer::Style {
|
||||
text_color: Color::WHITE,
|
||||
},
|
||||
&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
|
||||
renderer.with_primitives(|backend, primitive| {
|
||||
backend.present(
|
||||
&gl,
|
||||
primitive,
|
||||
&viewport,
|
||||
&debug.overlay(),
|
||||
);
|
||||
});
|
||||
|
||||
// Update the mouse cursor
|
||||
windowed_context.window().set_cursor_icon(
|
||||
iced_winit::conversion::mouse_interaction(
|
||||
state.mouse_interaction(),
|
||||
),
|
||||
);
|
||||
|
||||
windowed_context.swap_buffers().unwrap();
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
@ -1,102 +0,0 @@
|
|||
use glow::*;
|
||||
use iced_glow::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!("{shader_version}\n{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.bind_vertex_array(Some(self.vertex_array));
|
||||
gl.use_program(Some(self.program));
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -6,5 +6,4 @@ edition = "2021"
|
|||
publish = false
|
||||
|
||||
[dependencies]
|
||||
iced = { path = "../..", features = ["debug"] }
|
||||
iced_lazy = { path = "../../lazy" }
|
||||
iced = { path = "../..", features = ["debug", "lazy"] }
|
||||
|
|
|
|||
|
|
@ -1,10 +1,9 @@
|
|||
use iced::theme;
|
||||
use iced::widget::{
|
||||
button, column, horizontal_space, pick_list, row, scrollable, text,
|
||||
button, column, horizontal_space, lazy, pick_list, row, scrollable, text,
|
||||
text_input,
|
||||
};
|
||||
use iced::{Element, Length, Sandbox, Settings};
|
||||
use iced_lazy::lazy;
|
||||
|
||||
use std::collections::HashSet;
|
||||
use std::hash::Hash;
|
||||
|
|
@ -214,12 +213,9 @@ impl Sandbox for App {
|
|||
column![
|
||||
scrollable(options).height(Length::Fill),
|
||||
row![
|
||||
text_input(
|
||||
"Add a new option",
|
||||
&self.input,
|
||||
Message::InputChanged,
|
||||
)
|
||||
.on_submit(Message::AddItem(self.input.clone())),
|
||||
text_input("Add a new option", &self.input)
|
||||
.on_input(Message::InputChanged)
|
||||
.on_submit(Message::AddItem(self.input.clone())),
|
||||
button(text(format!("Toggle Order ({})", self.order)))
|
||||
.on_press(Message::ToggleOrder)
|
||||
]
|
||||
|
|
|
|||
11
examples/loading_spinners/Cargo.toml
Normal file
11
examples/loading_spinners/Cargo.toml
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
[package]
|
||||
name = "loading_spinners"
|
||||
version = "0.1.0"
|
||||
authors = ["Nick Senger <dev@nsenger.com>"]
|
||||
edition = "2021"
|
||||
publish = false
|
||||
|
||||
[dependencies]
|
||||
iced = { path = "../..", features = ["advanced", "canvas"] }
|
||||
lyon_algorithms = "1"
|
||||
once_cell = "1"
|
||||
14
examples/loading_spinners/README.md
Normal file
14
examples/loading_spinners/README.md
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
## Loading Spinners
|
||||
|
||||
Example implementation of animated indeterminate loading spinners.
|
||||
|
||||
<div align="center">
|
||||
<a href="https://gfycat.com/importantdevotedhammerheadbird">
|
||||
<img src="https://thumbs.gfycat.com/ImportantDevotedHammerheadbird-small.gif">
|
||||
</a>
|
||||
</div>
|
||||
|
||||
You can run it with `cargo run`:
|
||||
```
|
||||
cargo run --package loading_spinners
|
||||
```
|
||||
421
examples/loading_spinners/src/circular.rs
Normal file
421
examples/loading_spinners/src/circular.rs
Normal file
|
|
@ -0,0 +1,421 @@
|
|||
//! Show a circular progress indicator.
|
||||
use iced::advanced::layout;
|
||||
use iced::advanced::renderer;
|
||||
use iced::advanced::widget::tree::{self, Tree};
|
||||
use iced::advanced::{Clipboard, Layout, Renderer, Shell, Widget};
|
||||
use iced::event;
|
||||
use iced::mouse;
|
||||
use iced::time::Instant;
|
||||
use iced::widget::canvas;
|
||||
use iced::window::{self, RedrawRequest};
|
||||
use iced::{
|
||||
Background, Color, Element, Event, Length, Rectangle, Size, Vector,
|
||||
};
|
||||
|
||||
use super::easing::{self, Easing};
|
||||
|
||||
use std::f32::consts::PI;
|
||||
use std::time::Duration;
|
||||
|
||||
const MIN_RADIANS: f32 = PI / 8.0;
|
||||
const WRAP_RADIANS: f32 = 2.0 * PI - PI / 4.0;
|
||||
const BASE_ROTATION_SPEED: u32 = u32::MAX / 80;
|
||||
|
||||
#[allow(missing_debug_implementations)]
|
||||
pub struct Circular<'a, Theme>
|
||||
where
|
||||
Theme: StyleSheet,
|
||||
{
|
||||
size: f32,
|
||||
bar_height: f32,
|
||||
style: <Theme as StyleSheet>::Style,
|
||||
easing: &'a Easing,
|
||||
cycle_duration: Duration,
|
||||
rotation_duration: Duration,
|
||||
}
|
||||
|
||||
impl<'a, Theme> Circular<'a, Theme>
|
||||
where
|
||||
Theme: StyleSheet,
|
||||
{
|
||||
/// Creates a new [`Circular`] with the given content.
|
||||
pub fn new() -> Self {
|
||||
Circular {
|
||||
size: 40.0,
|
||||
bar_height: 4.0,
|
||||
style: <Theme as StyleSheet>::Style::default(),
|
||||
easing: &easing::STANDARD,
|
||||
cycle_duration: Duration::from_millis(600),
|
||||
rotation_duration: Duration::from_secs(2),
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the size of the [`Circular`].
|
||||
pub fn size(mut self, size: f32) -> Self {
|
||||
self.size = size;
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the bar height of the [`Circular`].
|
||||
pub fn bar_height(mut self, bar_height: f32) -> Self {
|
||||
self.bar_height = bar_height;
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the style variant of this [`Circular`].
|
||||
pub fn style(mut self, style: <Theme as StyleSheet>::Style) -> Self {
|
||||
self.style = style;
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the easing of this [`Circular`].
|
||||
pub fn easing(mut self, easing: &'a Easing) -> Self {
|
||||
self.easing = easing;
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the cycle duration of this [`Circular`].
|
||||
pub fn cycle_duration(mut self, duration: Duration) -> Self {
|
||||
self.cycle_duration = duration / 2;
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the base rotation duration of this [`Circular`]. This is the duration that a full
|
||||
/// rotation would take if the cycle rotation were set to 0.0 (no expanding or contracting)
|
||||
pub fn rotation_duration(mut self, duration: Duration) -> Self {
|
||||
self.rotation_duration = duration;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, Theme> Default for Circular<'a, Theme>
|
||||
where
|
||||
Theme: StyleSheet,
|
||||
{
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
enum Animation {
|
||||
Expanding {
|
||||
start: Instant,
|
||||
progress: f32,
|
||||
rotation: u32,
|
||||
last: Instant,
|
||||
},
|
||||
Contracting {
|
||||
start: Instant,
|
||||
progress: f32,
|
||||
rotation: u32,
|
||||
last: Instant,
|
||||
},
|
||||
}
|
||||
|
||||
impl Default for Animation {
|
||||
fn default() -> Self {
|
||||
Self::Expanding {
|
||||
start: Instant::now(),
|
||||
progress: 0.0,
|
||||
rotation: 0,
|
||||
last: Instant::now(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Animation {
|
||||
fn next(&self, additional_rotation: u32, now: Instant) -> Self {
|
||||
match self {
|
||||
Self::Expanding { rotation, .. } => Self::Contracting {
|
||||
start: now,
|
||||
progress: 0.0,
|
||||
rotation: rotation.wrapping_add(additional_rotation),
|
||||
last: now,
|
||||
},
|
||||
Self::Contracting { rotation, .. } => Self::Expanding {
|
||||
start: now,
|
||||
progress: 0.0,
|
||||
rotation: rotation.wrapping_add(
|
||||
BASE_ROTATION_SPEED.wrapping_add(
|
||||
((WRAP_RADIANS / (2.0 * PI)) * u32::MAX as f32) as u32,
|
||||
),
|
||||
),
|
||||
last: now,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn start(&self) -> Instant {
|
||||
match self {
|
||||
Self::Expanding { start, .. } | Self::Contracting { start, .. } => {
|
||||
*start
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn last(&self) -> Instant {
|
||||
match self {
|
||||
Self::Expanding { last, .. } | Self::Contracting { last, .. } => {
|
||||
*last
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn timed_transition(
|
||||
&self,
|
||||
cycle_duration: Duration,
|
||||
rotation_duration: Duration,
|
||||
now: Instant,
|
||||
) -> Self {
|
||||
let elapsed = now.duration_since(self.start());
|
||||
let additional_rotation = ((now - self.last()).as_secs_f32()
|
||||
/ rotation_duration.as_secs_f32()
|
||||
* (u32::MAX) as f32) as u32;
|
||||
|
||||
match elapsed {
|
||||
elapsed if elapsed > cycle_duration => {
|
||||
self.next(additional_rotation, now)
|
||||
}
|
||||
_ => self.with_elapsed(
|
||||
cycle_duration,
|
||||
additional_rotation,
|
||||
elapsed,
|
||||
now,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
fn with_elapsed(
|
||||
&self,
|
||||
cycle_duration: Duration,
|
||||
additional_rotation: u32,
|
||||
elapsed: Duration,
|
||||
now: Instant,
|
||||
) -> Self {
|
||||
let progress = elapsed.as_secs_f32() / cycle_duration.as_secs_f32();
|
||||
match self {
|
||||
Self::Expanding {
|
||||
start, rotation, ..
|
||||
} => Self::Expanding {
|
||||
start: *start,
|
||||
progress,
|
||||
rotation: rotation.wrapping_add(additional_rotation),
|
||||
last: now,
|
||||
},
|
||||
Self::Contracting {
|
||||
start, rotation, ..
|
||||
} => Self::Contracting {
|
||||
start: *start,
|
||||
progress,
|
||||
rotation: rotation.wrapping_add(additional_rotation),
|
||||
last: now,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn rotation(&self) -> f32 {
|
||||
match self {
|
||||
Self::Expanding { rotation, .. }
|
||||
| Self::Contracting { rotation, .. } => {
|
||||
*rotation as f32 / u32::MAX as f32
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct State {
|
||||
animation: Animation,
|
||||
cache: canvas::Cache,
|
||||
}
|
||||
|
||||
impl<'a, Message, Theme> Widget<Message, iced::Renderer<Theme>>
|
||||
for Circular<'a, Theme>
|
||||
where
|
||||
Message: 'a + Clone,
|
||||
Theme: StyleSheet,
|
||||
{
|
||||
fn tag(&self) -> tree::Tag {
|
||||
tree::Tag::of::<State>()
|
||||
}
|
||||
|
||||
fn state(&self) -> tree::State {
|
||||
tree::State::new(State::default())
|
||||
}
|
||||
|
||||
fn width(&self) -> Length {
|
||||
Length::Fixed(self.size)
|
||||
}
|
||||
|
||||
fn height(&self) -> Length {
|
||||
Length::Fixed(self.size)
|
||||
}
|
||||
|
||||
fn layout(
|
||||
&self,
|
||||
_renderer: &iced::Renderer<Theme>,
|
||||
limits: &layout::Limits,
|
||||
) -> layout::Node {
|
||||
let limits = limits.width(self.size).height(self.size);
|
||||
let size = limits.resolve(Size::ZERO);
|
||||
|
||||
layout::Node::new(size)
|
||||
}
|
||||
|
||||
fn on_event(
|
||||
&mut self,
|
||||
tree: &mut Tree,
|
||||
event: Event,
|
||||
_layout: Layout<'_>,
|
||||
_cursor: mouse::Cursor,
|
||||
_renderer: &iced::Renderer<Theme>,
|
||||
_clipboard: &mut dyn Clipboard,
|
||||
shell: &mut Shell<'_, Message>,
|
||||
) -> event::Status {
|
||||
const FRAME_RATE: u64 = 60;
|
||||
|
||||
let state = tree.state.downcast_mut::<State>();
|
||||
|
||||
if let Event::Window(window::Event::RedrawRequested(now)) = event {
|
||||
state.animation = state.animation.timed_transition(
|
||||
self.cycle_duration,
|
||||
self.rotation_duration,
|
||||
now,
|
||||
);
|
||||
|
||||
state.cache.clear();
|
||||
shell.request_redraw(RedrawRequest::At(
|
||||
now + Duration::from_millis(1000 / FRAME_RATE),
|
||||
));
|
||||
}
|
||||
|
||||
event::Status::Ignored
|
||||
}
|
||||
|
||||
fn draw(
|
||||
&self,
|
||||
tree: &Tree,
|
||||
renderer: &mut iced::Renderer<Theme>,
|
||||
theme: &Theme,
|
||||
_style: &renderer::Style,
|
||||
layout: Layout<'_>,
|
||||
_cursor: mouse::Cursor,
|
||||
_viewport: &Rectangle,
|
||||
) {
|
||||
let state = tree.state.downcast_ref::<State>();
|
||||
let bounds = layout.bounds();
|
||||
let custom_style =
|
||||
<Theme as StyleSheet>::appearance(theme, &self.style);
|
||||
|
||||
let geometry = state.cache.draw(renderer, bounds.size(), |frame| {
|
||||
let track_radius = frame.width() / 2.0 - self.bar_height;
|
||||
let track_path = canvas::Path::circle(frame.center(), track_radius);
|
||||
|
||||
frame.stroke(
|
||||
&track_path,
|
||||
canvas::Stroke::default()
|
||||
.with_color(custom_style.track_color)
|
||||
.with_width(self.bar_height),
|
||||
);
|
||||
|
||||
let mut builder = canvas::path::Builder::new();
|
||||
|
||||
let start = state.animation.rotation() * 2.0 * PI;
|
||||
|
||||
match state.animation {
|
||||
Animation::Expanding { progress, .. } => {
|
||||
builder.arc(canvas::path::Arc {
|
||||
center: frame.center(),
|
||||
radius: track_radius,
|
||||
start_angle: start,
|
||||
end_angle: start
|
||||
+ MIN_RADIANS
|
||||
+ WRAP_RADIANS * (self.easing.y_at_x(progress)),
|
||||
});
|
||||
}
|
||||
Animation::Contracting { progress, .. } => {
|
||||
builder.arc(canvas::path::Arc {
|
||||
center: frame.center(),
|
||||
radius: track_radius,
|
||||
start_angle: start
|
||||
+ WRAP_RADIANS * (self.easing.y_at_x(progress)),
|
||||
end_angle: start + MIN_RADIANS + WRAP_RADIANS,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
let bar_path = builder.build();
|
||||
|
||||
frame.stroke(
|
||||
&bar_path,
|
||||
canvas::Stroke::default()
|
||||
.with_color(custom_style.bar_color)
|
||||
.with_width(self.bar_height),
|
||||
);
|
||||
});
|
||||
|
||||
renderer.with_translation(
|
||||
Vector::new(bounds.x, bounds.y),
|
||||
|renderer| {
|
||||
use iced::advanced::graphics::geometry::Renderer as _;
|
||||
|
||||
renderer.draw(vec![geometry]);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, Message, Theme> From<Circular<'a, Theme>>
|
||||
for Element<'a, Message, iced::Renderer<Theme>>
|
||||
where
|
||||
Message: Clone + 'a,
|
||||
Theme: StyleSheet + 'a,
|
||||
{
|
||||
fn from(circular: Circular<'a, Theme>) -> Self {
|
||||
Self::new(circular)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct Appearance {
|
||||
/// The [`Background`] of the progress indicator.
|
||||
pub background: Option<Background>,
|
||||
/// The track [`Color`] of the progress indicator.
|
||||
pub track_color: Color,
|
||||
/// The bar [`Color`] of the progress indicator.
|
||||
pub bar_color: Color,
|
||||
}
|
||||
|
||||
impl std::default::Default for Appearance {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
background: None,
|
||||
track_color: Color::TRANSPARENT,
|
||||
bar_color: Color::BLACK,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A set of rules that dictate the style of an indicator.
|
||||
pub trait StyleSheet {
|
||||
/// The supported style of the [`StyleSheet`].
|
||||
type Style: Default;
|
||||
|
||||
/// Produces the active [`Appearance`] of a indicator.
|
||||
fn appearance(&self, style: &Self::Style) -> Appearance;
|
||||
}
|
||||
|
||||
impl StyleSheet for iced::Theme {
|
||||
type Style = ();
|
||||
|
||||
fn appearance(&self, _style: &Self::Style) -> Appearance {
|
||||
let palette = self.extended_palette();
|
||||
|
||||
Appearance {
|
||||
background: None,
|
||||
track_color: palette.background.weak.color,
|
||||
bar_color: palette.primary.base.color,
|
||||
}
|
||||
}
|
||||
}
|
||||
133
examples/loading_spinners/src/easing.rs
Normal file
133
examples/loading_spinners/src/easing.rs
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
use iced::Point;
|
||||
|
||||
use lyon_algorithms::measure::PathMeasurements;
|
||||
use lyon_algorithms::path::{builder::NoAttributes, path::BuilderImpl, Path};
|
||||
use once_cell::sync::Lazy;
|
||||
|
||||
pub static EMPHASIZED: Lazy<Easing> = Lazy::new(|| {
|
||||
Easing::builder()
|
||||
.cubic_bezier_to([0.05, 0.0], [0.133333, 0.06], [0.166666, 0.4])
|
||||
.cubic_bezier_to([0.208333, 0.82], [0.25, 1.0], [1.0, 1.0])
|
||||
.build()
|
||||
});
|
||||
|
||||
pub static EMPHASIZED_DECELERATE: Lazy<Easing> = Lazy::new(|| {
|
||||
Easing::builder()
|
||||
.cubic_bezier_to([0.05, 0.7], [0.1, 1.0], [1.0, 1.0])
|
||||
.build()
|
||||
});
|
||||
|
||||
pub static EMPHASIZED_ACCELERATE: Lazy<Easing> = Lazy::new(|| {
|
||||
Easing::builder()
|
||||
.cubic_bezier_to([0.3, 0.0], [0.8, 0.15], [1.0, 1.0])
|
||||
.build()
|
||||
});
|
||||
|
||||
pub static STANDARD: Lazy<Easing> = Lazy::new(|| {
|
||||
Easing::builder()
|
||||
.cubic_bezier_to([0.2, 0.0], [0.0, 1.0], [1.0, 1.0])
|
||||
.build()
|
||||
});
|
||||
|
||||
pub static STANDARD_DECELERATE: Lazy<Easing> = Lazy::new(|| {
|
||||
Easing::builder()
|
||||
.cubic_bezier_to([0.0, 0.0], [0.0, 1.0], [1.0, 1.0])
|
||||
.build()
|
||||
});
|
||||
|
||||
pub static STANDARD_ACCELERATE: Lazy<Easing> = Lazy::new(|| {
|
||||
Easing::builder()
|
||||
.cubic_bezier_to([0.3, 0.0], [1.0, 1.0], [1.0, 1.0])
|
||||
.build()
|
||||
});
|
||||
|
||||
pub struct Easing {
|
||||
path: Path,
|
||||
measurements: PathMeasurements,
|
||||
}
|
||||
|
||||
impl Easing {
|
||||
pub fn builder() -> Builder {
|
||||
Builder::new()
|
||||
}
|
||||
|
||||
pub fn y_at_x(&self, x: f32) -> f32 {
|
||||
let mut sampler = self.measurements.create_sampler(
|
||||
&self.path,
|
||||
lyon_algorithms::measure::SampleType::Normalized,
|
||||
);
|
||||
let sample = sampler.sample(x);
|
||||
|
||||
sample.position().y
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Builder(NoAttributes<BuilderImpl>);
|
||||
|
||||
impl Builder {
|
||||
pub fn new() -> Self {
|
||||
let mut builder = Path::builder();
|
||||
builder.begin(lyon_algorithms::geom::point(0.0, 0.0));
|
||||
|
||||
Self(builder)
|
||||
}
|
||||
|
||||
/// Adds a line segment. Points must be between 0,0 and 1,1
|
||||
pub fn line_to(mut self, to: impl Into<Point>) -> Self {
|
||||
self.0.line_to(Self::point(to));
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// Adds a quadratic bézier curve. Points must be between 0,0 and 1,1
|
||||
pub fn quadratic_bezier_to(
|
||||
mut self,
|
||||
ctrl: impl Into<Point>,
|
||||
to: impl Into<Point>,
|
||||
) -> Self {
|
||||
self.0
|
||||
.quadratic_bezier_to(Self::point(ctrl), Self::point(to));
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// Adds a cubic bézier curve. Points must be between 0,0 and 1,1
|
||||
pub fn cubic_bezier_to(
|
||||
mut self,
|
||||
ctrl1: impl Into<Point>,
|
||||
ctrl2: impl Into<Point>,
|
||||
to: impl Into<Point>,
|
||||
) -> Self {
|
||||
self.0.cubic_bezier_to(
|
||||
Self::point(ctrl1),
|
||||
Self::point(ctrl2),
|
||||
Self::point(to),
|
||||
);
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
pub fn build(mut self) -> Easing {
|
||||
self.0.line_to(lyon_algorithms::geom::point(1.0, 1.0));
|
||||
self.0.end(false);
|
||||
|
||||
let path = self.0.build();
|
||||
let measurements = PathMeasurements::from_path(&path, 0.0);
|
||||
|
||||
Easing { path, measurements }
|
||||
}
|
||||
|
||||
fn point(p: impl Into<Point>) -> lyon_algorithms::geom::Point<f32> {
|
||||
let p: Point = p.into();
|
||||
lyon_algorithms::geom::point(
|
||||
p.x.min(1.0).max(0.0),
|
||||
p.y.min(1.0).max(0.0),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Builder {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
326
examples/loading_spinners/src/linear.rs
Normal file
326
examples/loading_spinners/src/linear.rs
Normal file
|
|
@ -0,0 +1,326 @@
|
|||
//! Show a linear progress indicator.
|
||||
use iced::advanced::layout;
|
||||
use iced::advanced::renderer::{self, Quad};
|
||||
use iced::advanced::widget::tree::{self, Tree};
|
||||
use iced::advanced::{Clipboard, Layout, Shell, Widget};
|
||||
use iced::event;
|
||||
use iced::mouse;
|
||||
use iced::time::Instant;
|
||||
use iced::window::{self, RedrawRequest};
|
||||
use iced::{Background, Color, Element, Event, Length, Rectangle, Size};
|
||||
|
||||
use super::easing::{self, Easing};
|
||||
|
||||
use std::time::Duration;
|
||||
|
||||
#[allow(missing_debug_implementations)]
|
||||
pub struct Linear<'a, Renderer>
|
||||
where
|
||||
Renderer: iced::advanced::Renderer,
|
||||
Renderer::Theme: StyleSheet,
|
||||
{
|
||||
width: Length,
|
||||
height: Length,
|
||||
style: <Renderer::Theme as StyleSheet>::Style,
|
||||
easing: &'a Easing,
|
||||
cycle_duration: Duration,
|
||||
}
|
||||
|
||||
impl<'a, Renderer> Linear<'a, Renderer>
|
||||
where
|
||||
Renderer: iced::advanced::Renderer,
|
||||
Renderer::Theme: StyleSheet,
|
||||
{
|
||||
/// Creates a new [`Linear`] with the given content.
|
||||
pub fn new() -> Self {
|
||||
Linear {
|
||||
width: Length::Fixed(100.0),
|
||||
height: Length::Fixed(4.0),
|
||||
style: <Renderer::Theme as StyleSheet>::Style::default(),
|
||||
easing: &easing::STANDARD,
|
||||
cycle_duration: Duration::from_millis(600),
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the width of the [`Linear`].
|
||||
pub fn width(mut self, width: impl Into<Length>) -> Self {
|
||||
self.width = width.into();
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the height of the [`Linear`].
|
||||
pub fn height(mut self, height: impl Into<Length>) -> Self {
|
||||
self.height = height.into();
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the style variant of this [`Linear`].
|
||||
pub fn style(
|
||||
mut self,
|
||||
style: <Renderer::Theme as StyleSheet>::Style,
|
||||
) -> Self {
|
||||
self.style = style;
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the motion easing of this [`Linear`].
|
||||
pub fn easing(mut self, easing: &'a Easing) -> Self {
|
||||
self.easing = easing;
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the cycle duration of this [`Linear`].
|
||||
pub fn cycle_duration(mut self, duration: Duration) -> Self {
|
||||
self.cycle_duration = duration / 2;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, Renderer> Default for Linear<'a, Renderer>
|
||||
where
|
||||
Renderer: iced::advanced::Renderer,
|
||||
Renderer::Theme: StyleSheet,
|
||||
{
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
enum State {
|
||||
Expanding { start: Instant, progress: f32 },
|
||||
Contracting { start: Instant, progress: f32 },
|
||||
}
|
||||
|
||||
impl Default for State {
|
||||
fn default() -> Self {
|
||||
Self::Expanding {
|
||||
start: Instant::now(),
|
||||
progress: 0.0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl State {
|
||||
fn next(&self, now: Instant) -> Self {
|
||||
match self {
|
||||
Self::Expanding { .. } => Self::Contracting {
|
||||
start: now,
|
||||
progress: 0.0,
|
||||
},
|
||||
Self::Contracting { .. } => Self::Expanding {
|
||||
start: now,
|
||||
progress: 0.0,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn start(&self) -> Instant {
|
||||
match self {
|
||||
Self::Expanding { start, .. } | Self::Contracting { start, .. } => {
|
||||
*start
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn timed_transition(&self, cycle_duration: Duration, now: Instant) -> Self {
|
||||
let elapsed = now.duration_since(self.start());
|
||||
|
||||
match elapsed {
|
||||
elapsed if elapsed > cycle_duration => self.next(now),
|
||||
_ => self.with_elapsed(cycle_duration, elapsed),
|
||||
}
|
||||
}
|
||||
|
||||
fn with_elapsed(
|
||||
&self,
|
||||
cycle_duration: Duration,
|
||||
elapsed: Duration,
|
||||
) -> Self {
|
||||
let progress = elapsed.as_secs_f32() / cycle_duration.as_secs_f32();
|
||||
match self {
|
||||
Self::Expanding { start, .. } => Self::Expanding {
|
||||
start: *start,
|
||||
progress,
|
||||
},
|
||||
Self::Contracting { start, .. } => Self::Contracting {
|
||||
start: *start,
|
||||
progress,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, Message, Renderer> Widget<Message, Renderer> for Linear<'a, Renderer>
|
||||
where
|
||||
Message: 'a + Clone,
|
||||
Renderer: 'a + iced::advanced::Renderer,
|
||||
Renderer::Theme: StyleSheet,
|
||||
{
|
||||
fn tag(&self) -> tree::Tag {
|
||||
tree::Tag::of::<State>()
|
||||
}
|
||||
|
||||
fn state(&self) -> tree::State {
|
||||
tree::State::new(State::default())
|
||||
}
|
||||
|
||||
fn width(&self) -> Length {
|
||||
self.width
|
||||
}
|
||||
|
||||
fn height(&self) -> Length {
|
||||
self.height
|
||||
}
|
||||
|
||||
fn layout(
|
||||
&self,
|
||||
_renderer: &Renderer,
|
||||
limits: &layout::Limits,
|
||||
) -> layout::Node {
|
||||
let limits = limits.width(self.width).height(self.height);
|
||||
let size = limits.resolve(Size::ZERO);
|
||||
|
||||
layout::Node::new(size)
|
||||
}
|
||||
|
||||
fn on_event(
|
||||
&mut self,
|
||||
tree: &mut Tree,
|
||||
event: Event,
|
||||
_layout: Layout<'_>,
|
||||
_cursor: mouse::Cursor,
|
||||
_renderer: &Renderer,
|
||||
_clipboard: &mut dyn Clipboard,
|
||||
shell: &mut Shell<'_, Message>,
|
||||
) -> event::Status {
|
||||
const FRAME_RATE: u64 = 60;
|
||||
|
||||
let state = tree.state.downcast_mut::<State>();
|
||||
|
||||
if let Event::Window(window::Event::RedrawRequested(now)) = event {
|
||||
*state = state.timed_transition(self.cycle_duration, now);
|
||||
|
||||
shell.request_redraw(RedrawRequest::At(
|
||||
now + Duration::from_millis(1000 / FRAME_RATE),
|
||||
));
|
||||
}
|
||||
|
||||
event::Status::Ignored
|
||||
}
|
||||
|
||||
fn draw(
|
||||
&self,
|
||||
tree: &Tree,
|
||||
renderer: &mut Renderer,
|
||||
theme: &Renderer::Theme,
|
||||
_style: &renderer::Style,
|
||||
layout: Layout<'_>,
|
||||
_cursor: mouse::Cursor,
|
||||
_viewport: &Rectangle,
|
||||
) {
|
||||
let bounds = layout.bounds();
|
||||
let custom_style = theme.appearance(&self.style);
|
||||
let state = tree.state.downcast_ref::<State>();
|
||||
|
||||
renderer.fill_quad(
|
||||
renderer::Quad {
|
||||
bounds: Rectangle {
|
||||
x: bounds.x,
|
||||
y: bounds.y,
|
||||
width: bounds.width,
|
||||
height: bounds.height,
|
||||
},
|
||||
border_radius: 0.0.into(),
|
||||
border_width: 0.0,
|
||||
border_color: Color::TRANSPARENT,
|
||||
},
|
||||
Background::Color(custom_style.track_color),
|
||||
);
|
||||
|
||||
match state {
|
||||
State::Expanding { progress, .. } => renderer.fill_quad(
|
||||
renderer::Quad {
|
||||
bounds: Rectangle {
|
||||
x: bounds.x,
|
||||
y: bounds.y,
|
||||
width: self.easing.y_at_x(*progress) * bounds.width,
|
||||
height: bounds.height,
|
||||
},
|
||||
border_radius: 0.0.into(),
|
||||
border_width: 0.0,
|
||||
border_color: Color::TRANSPARENT,
|
||||
},
|
||||
Background::Color(custom_style.bar_color),
|
||||
),
|
||||
|
||||
State::Contracting { progress, .. } => renderer.fill_quad(
|
||||
Quad {
|
||||
bounds: Rectangle {
|
||||
x: bounds.x
|
||||
+ self.easing.y_at_x(*progress) * bounds.width,
|
||||
y: bounds.y,
|
||||
width: (1.0 - self.easing.y_at_x(*progress))
|
||||
* bounds.width,
|
||||
height: bounds.height,
|
||||
},
|
||||
border_radius: 0.0.into(),
|
||||
border_width: 0.0,
|
||||
border_color: Color::TRANSPARENT,
|
||||
},
|
||||
Background::Color(custom_style.bar_color),
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, Message, Renderer> From<Linear<'a, Renderer>>
|
||||
for Element<'a, Message, Renderer>
|
||||
where
|
||||
Message: Clone + 'a,
|
||||
Renderer: iced::advanced::Renderer + 'a,
|
||||
Renderer::Theme: StyleSheet,
|
||||
{
|
||||
fn from(linear: Linear<'a, Renderer>) -> Self {
|
||||
Self::new(linear)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct Appearance {
|
||||
/// The track [`Color`] of the progress indicator.
|
||||
pub track_color: Color,
|
||||
/// The bar [`Color`] of the progress indicator.
|
||||
pub bar_color: Color,
|
||||
}
|
||||
|
||||
impl std::default::Default for Appearance {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
track_color: Color::TRANSPARENT,
|
||||
bar_color: Color::BLACK,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A set of rules that dictate the style of an indicator.
|
||||
pub trait StyleSheet {
|
||||
/// The supported style of the [`StyleSheet`].
|
||||
type Style: Default;
|
||||
|
||||
/// Produces the active [`Appearance`] of a indicator.
|
||||
fn appearance(&self, style: &Self::Style) -> Appearance;
|
||||
}
|
||||
|
||||
impl StyleSheet for iced::Theme {
|
||||
type Style = ();
|
||||
|
||||
fn appearance(&self, _style: &Self::Style) -> Appearance {
|
||||
let palette = self.extended_palette();
|
||||
|
||||
Appearance {
|
||||
track_color: palette.background.weak.color,
|
||||
bar_color: palette.primary.base.color,
|
||||
}
|
||||
}
|
||||
}
|
||||
118
examples/loading_spinners/src/main.rs
Normal file
118
examples/loading_spinners/src/main.rs
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
use iced::executor;
|
||||
use iced::widget::{column, container, row, slider, text};
|
||||
use iced::{Application, Command, Element, Length, Settings, Theme};
|
||||
|
||||
use std::time::Duration;
|
||||
|
||||
mod circular;
|
||||
mod easing;
|
||||
mod linear;
|
||||
|
||||
use circular::Circular;
|
||||
use linear::Linear;
|
||||
|
||||
pub fn main() -> iced::Result {
|
||||
LoadingSpinners::run(Settings {
|
||||
antialiasing: true,
|
||||
..Default::default()
|
||||
})
|
||||
}
|
||||
|
||||
struct LoadingSpinners {
|
||||
cycle_duration: f32,
|
||||
}
|
||||
|
||||
impl Default for LoadingSpinners {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
cycle_duration: 2.0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
enum Message {
|
||||
CycleDurationChanged(f32),
|
||||
}
|
||||
|
||||
impl Application for LoadingSpinners {
|
||||
type Message = Message;
|
||||
type Flags = ();
|
||||
type Executor = executor::Default;
|
||||
type Theme = Theme;
|
||||
|
||||
fn new(_flags: Self::Flags) -> (Self, Command<Message>) {
|
||||
(Self::default(), Command::none())
|
||||
}
|
||||
|
||||
fn title(&self) -> String {
|
||||
String::from("Loading Spinners - Iced")
|
||||
}
|
||||
|
||||
fn update(&mut self, message: Message) -> Command<Message> {
|
||||
match message {
|
||||
Message::CycleDurationChanged(duration) => {
|
||||
self.cycle_duration = duration;
|
||||
}
|
||||
}
|
||||
|
||||
Command::none()
|
||||
}
|
||||
|
||||
fn view(&self) -> Element<Message> {
|
||||
let column = [
|
||||
&easing::EMPHASIZED,
|
||||
&easing::EMPHASIZED_DECELERATE,
|
||||
&easing::EMPHASIZED_ACCELERATE,
|
||||
&easing::STANDARD,
|
||||
&easing::STANDARD_DECELERATE,
|
||||
&easing::STANDARD_ACCELERATE,
|
||||
]
|
||||
.iter()
|
||||
.zip([
|
||||
"Emphasized:",
|
||||
"Emphasized Decelerate:",
|
||||
"Emphasized Accelerate:",
|
||||
"Standard:",
|
||||
"Standard Decelerate:",
|
||||
"Standard Accelerate:",
|
||||
])
|
||||
.fold(column![], |column, (easing, label)| {
|
||||
column.push(
|
||||
row![
|
||||
text(label).width(250),
|
||||
Linear::new().easing(easing).cycle_duration(
|
||||
Duration::from_secs_f32(self.cycle_duration)
|
||||
),
|
||||
Circular::new().easing(easing).cycle_duration(
|
||||
Duration::from_secs_f32(self.cycle_duration)
|
||||
)
|
||||
]
|
||||
.align_items(iced::Alignment::Center)
|
||||
.spacing(20.0),
|
||||
)
|
||||
})
|
||||
.spacing(20);
|
||||
|
||||
container(
|
||||
column.push(
|
||||
row(vec![
|
||||
text("Cycle duration:").into(),
|
||||
slider(1.0..=1000.0, self.cycle_duration * 100.0, |x| {
|
||||
Message::CycleDurationChanged(x / 100.0)
|
||||
})
|
||||
.width(200.0)
|
||||
.into(),
|
||||
text(format!("{:.2}s", self.cycle_duration)).into(),
|
||||
])
|
||||
.align_items(iced::Alignment::Center)
|
||||
.spacing(20.0),
|
||||
),
|
||||
)
|
||||
.width(Length::Fill)
|
||||
.height(Length::Fill)
|
||||
.center_x()
|
||||
.center_y()
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
|
@ -6,5 +6,4 @@ edition = "2021"
|
|||
publish = false
|
||||
|
||||
[dependencies]
|
||||
iced = { path = "../..", features = [] }
|
||||
iced_native = { path = "../../native" }
|
||||
iced = { path = "../..", features = ["advanced"] }
|
||||
|
|
|
|||
|
|
@ -1,12 +1,15 @@
|
|||
use iced::executor;
|
||||
use iced::keyboard;
|
||||
use iced::subscription::{self, Subscription};
|
||||
use iced::theme;
|
||||
use iced::widget::{
|
||||
self, button, column, container, horizontal_space, row, text, text_input,
|
||||
};
|
||||
use iced::{
|
||||
executor, keyboard, subscription, theme, Alignment, Application, Command,
|
||||
Element, Event, Length, Settings, Subscription,
|
||||
self, button, column, container, horizontal_space, pick_list, row, text,
|
||||
text_input,
|
||||
};
|
||||
use iced::{Alignment, Application, Command, Element, Event, Length, Settings};
|
||||
|
||||
use self::modal::Modal;
|
||||
use modal::Modal;
|
||||
use std::fmt;
|
||||
|
||||
pub fn main() -> iced::Result {
|
||||
App::run(Settings::default())
|
||||
|
|
@ -17,6 +20,7 @@ struct App {
|
|||
show_modal: bool,
|
||||
email: String,
|
||||
password: String,
|
||||
plan: Plan,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
|
|
@ -25,6 +29,7 @@ enum Message {
|
|||
HideModal,
|
||||
Email(String),
|
||||
Password(String),
|
||||
Plan(Plan),
|
||||
Submit,
|
||||
Event(Event),
|
||||
}
|
||||
|
|
@ -65,6 +70,10 @@ impl Application for App {
|
|||
self.password = password;
|
||||
Command::none()
|
||||
}
|
||||
Message::Plan(plan) => {
|
||||
self.plan = plan;
|
||||
Command::none()
|
||||
}
|
||||
Message::Submit => {
|
||||
if !self.email.is_empty() && !self.password.is_empty() {
|
||||
self.hide_modal();
|
||||
|
|
@ -133,23 +142,31 @@ impl Application for App {
|
|||
column![
|
||||
column![
|
||||
text("Email").size(12),
|
||||
text_input(
|
||||
"abc@123.com",
|
||||
&self.email,
|
||||
Message::Email
|
||||
)
|
||||
.on_submit(Message::Submit)
|
||||
.padding(5),
|
||||
text_input("abc@123.com", &self.email,)
|
||||
.on_input(Message::Email)
|
||||
.on_submit(Message::Submit)
|
||||
.padding(5),
|
||||
]
|
||||
.spacing(5),
|
||||
column![
|
||||
text("Password").size(12),
|
||||
text_input("", &self.password, Message::Password)
|
||||
text_input("", &self.password)
|
||||
.on_input(Message::Password)
|
||||
.on_submit(Message::Submit)
|
||||
.password()
|
||||
.padding(5),
|
||||
]
|
||||
.spacing(5),
|
||||
column![
|
||||
text("Plan").size(12),
|
||||
pick_list(
|
||||
Plan::ALL,
|
||||
Some(self.plan),
|
||||
Message::Plan
|
||||
)
|
||||
.padding(5),
|
||||
]
|
||||
.spacing(5),
|
||||
button(text("Submit")).on_press(Message::HideModal),
|
||||
]
|
||||
.spacing(10)
|
||||
|
|
@ -177,13 +194,39 @@ impl App {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
|
||||
enum Plan {
|
||||
#[default]
|
||||
Basic,
|
||||
Pro,
|
||||
Enterprise,
|
||||
}
|
||||
|
||||
impl Plan {
|
||||
pub const ALL: &[Self] = &[Self::Basic, Self::Pro, Self::Enterprise];
|
||||
}
|
||||
|
||||
impl fmt::Display for Plan {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Plan::Basic => "Basic",
|
||||
Plan::Pro => "Pro",
|
||||
Plan::Enterprise => "Enterprise",
|
||||
}
|
||||
.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
mod modal {
|
||||
use iced_native::alignment::Alignment;
|
||||
use iced_native::widget::{self, Tree};
|
||||
use iced_native::{
|
||||
event, layout, mouse, overlay, renderer, Clipboard, Color, Element,
|
||||
Event, Layout, Length, Point, Rectangle, Shell, Size, Widget,
|
||||
};
|
||||
use iced::advanced::layout::{self, Layout};
|
||||
use iced::advanced::overlay;
|
||||
use iced::advanced::renderer;
|
||||
use iced::advanced::widget::{self, Widget};
|
||||
use iced::advanced::{self, Clipboard, Shell};
|
||||
use iced::alignment::Alignment;
|
||||
use iced::event;
|
||||
use iced::mouse;
|
||||
use iced::{Color, Element, Event, Length, Point, Rectangle, Size};
|
||||
|
||||
/// A widget that centers a modal element over some base element
|
||||
pub struct Modal<'a, Message, Renderer> {
|
||||
|
|
@ -218,14 +261,17 @@ mod modal {
|
|||
impl<'a, Message, Renderer> Widget<Message, Renderer>
|
||||
for Modal<'a, Message, Renderer>
|
||||
where
|
||||
Renderer: iced_native::Renderer,
|
||||
Renderer: advanced::Renderer,
|
||||
Message: Clone,
|
||||
{
|
||||
fn children(&self) -> Vec<Tree> {
|
||||
vec![Tree::new(&self.base), Tree::new(&self.modal)]
|
||||
fn children(&self) -> Vec<widget::Tree> {
|
||||
vec![
|
||||
widget::Tree::new(&self.base),
|
||||
widget::Tree::new(&self.modal),
|
||||
]
|
||||
}
|
||||
|
||||
fn diff(&self, tree: &mut Tree) {
|
||||
fn diff(&self, tree: &mut widget::Tree) {
|
||||
tree.diff_children(&[&self.base, &self.modal]);
|
||||
}
|
||||
|
||||
|
|
@ -247,10 +293,10 @@ mod modal {
|
|||
|
||||
fn on_event(
|
||||
&mut self,
|
||||
state: &mut Tree,
|
||||
state: &mut widget::Tree,
|
||||
event: Event,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
cursor: mouse::Cursor,
|
||||
renderer: &Renderer,
|
||||
clipboard: &mut dyn Clipboard,
|
||||
shell: &mut Shell<'_, Message>,
|
||||
|
|
@ -259,7 +305,7 @@ mod modal {
|
|||
&mut state.children[0],
|
||||
event,
|
||||
layout,
|
||||
cursor_position,
|
||||
cursor,
|
||||
renderer,
|
||||
clipboard,
|
||||
shell,
|
||||
|
|
@ -268,12 +314,12 @@ mod modal {
|
|||
|
||||
fn draw(
|
||||
&self,
|
||||
state: &Tree,
|
||||
state: &widget::Tree,
|
||||
renderer: &mut Renderer,
|
||||
theme: &<Renderer as iced_native::Renderer>::Theme,
|
||||
theme: &<Renderer as advanced::Renderer>::Theme,
|
||||
style: &renderer::Style,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
cursor: mouse::Cursor,
|
||||
viewport: &Rectangle,
|
||||
) {
|
||||
self.base.as_widget().draw(
|
||||
|
|
@ -282,14 +328,14 @@ mod modal {
|
|||
theme,
|
||||
style,
|
||||
layout,
|
||||
cursor_position,
|
||||
cursor,
|
||||
viewport,
|
||||
);
|
||||
}
|
||||
|
||||
fn overlay<'b>(
|
||||
&'b mut self,
|
||||
state: &'b mut Tree,
|
||||
state: &'b mut widget::Tree,
|
||||
layout: Layout<'_>,
|
||||
_renderer: &Renderer,
|
||||
) -> Option<overlay::Element<'b, Message, Renderer>> {
|
||||
|
|
@ -306,16 +352,16 @@ mod modal {
|
|||
|
||||
fn mouse_interaction(
|
||||
&self,
|
||||
state: &Tree,
|
||||
state: &widget::Tree,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
cursor: mouse::Cursor,
|
||||
viewport: &Rectangle,
|
||||
renderer: &Renderer,
|
||||
) -> mouse::Interaction {
|
||||
self.base.as_widget().mouse_interaction(
|
||||
&state.children[0],
|
||||
layout,
|
||||
cursor_position,
|
||||
cursor,
|
||||
viewport,
|
||||
renderer,
|
||||
)
|
||||
|
|
@ -323,7 +369,7 @@ mod modal {
|
|||
|
||||
fn operate(
|
||||
&self,
|
||||
state: &mut Tree,
|
||||
state: &mut widget::Tree,
|
||||
layout: Layout<'_>,
|
||||
renderer: &Renderer,
|
||||
operation: &mut dyn widget::Operation<Message>,
|
||||
|
|
@ -339,7 +385,7 @@ mod modal {
|
|||
|
||||
struct Overlay<'a, 'b, Message, Renderer> {
|
||||
content: &'b mut Element<'a, Message, Renderer>,
|
||||
tree: &'b mut Tree,
|
||||
tree: &'b mut widget::Tree,
|
||||
size: Size,
|
||||
on_blur: Option<Message>,
|
||||
}
|
||||
|
|
@ -347,7 +393,7 @@ mod modal {
|
|||
impl<'a, 'b, Message, Renderer> overlay::Overlay<Message, Renderer>
|
||||
for Overlay<'a, 'b, Message, Renderer>
|
||||
where
|
||||
Renderer: iced_native::Renderer,
|
||||
Renderer: advanced::Renderer,
|
||||
Message: Clone,
|
||||
{
|
||||
fn layout(
|
||||
|
|
@ -373,7 +419,7 @@ mod modal {
|
|||
&mut self,
|
||||
event: Event,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
cursor: mouse::Cursor,
|
||||
renderer: &Renderer,
|
||||
clipboard: &mut dyn Clipboard,
|
||||
shell: &mut Shell<'_, Message>,
|
||||
|
|
@ -385,7 +431,7 @@ mod modal {
|
|||
mouse::Button::Left,
|
||||
)) = &event
|
||||
{
|
||||
if !content_bounds.contains(cursor_position) {
|
||||
if !cursor.is_over(content_bounds) {
|
||||
shell.publish(message.clone());
|
||||
return event::Status::Captured;
|
||||
}
|
||||
|
|
@ -396,7 +442,7 @@ mod modal {
|
|||
self.tree,
|
||||
event,
|
||||
layout.children().next().unwrap(),
|
||||
cursor_position,
|
||||
cursor,
|
||||
renderer,
|
||||
clipboard,
|
||||
shell,
|
||||
|
|
@ -409,12 +455,12 @@ mod modal {
|
|||
theme: &Renderer::Theme,
|
||||
style: &renderer::Style,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
cursor: mouse::Cursor,
|
||||
) {
|
||||
renderer.fill_quad(
|
||||
renderer::Quad {
|
||||
bounds: layout.bounds(),
|
||||
border_radius: renderer::BorderRadius::from(0.0),
|
||||
border_radius: Default::default(),
|
||||
border_width: 0.0,
|
||||
border_color: Color::TRANSPARENT,
|
||||
},
|
||||
|
|
@ -430,7 +476,7 @@ mod modal {
|
|||
theme,
|
||||
style,
|
||||
layout.children().next().unwrap(),
|
||||
cursor_position,
|
||||
cursor,
|
||||
&layout.bounds(),
|
||||
);
|
||||
}
|
||||
|
|
@ -452,24 +498,36 @@ mod modal {
|
|||
fn mouse_interaction(
|
||||
&self,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
cursor: mouse::Cursor,
|
||||
viewport: &Rectangle,
|
||||
renderer: &Renderer,
|
||||
) -> mouse::Interaction {
|
||||
self.content.as_widget().mouse_interaction(
|
||||
self.tree,
|
||||
layout.children().next().unwrap(),
|
||||
cursor_position,
|
||||
cursor,
|
||||
viewport,
|
||||
renderer,
|
||||
)
|
||||
}
|
||||
|
||||
fn overlay<'c>(
|
||||
&'c mut self,
|
||||
layout: Layout<'_>,
|
||||
renderer: &Renderer,
|
||||
) -> Option<overlay::Element<'c, Message, Renderer>> {
|
||||
self.content.as_widget_mut().overlay(
|
||||
self.tree,
|
||||
layout.children().next().unwrap(),
|
||||
renderer,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, Message, Renderer> From<Modal<'a, Message, Renderer>>
|
||||
for Element<'a, Message, Renderer>
|
||||
where
|
||||
Renderer: 'a + iced_native::Renderer,
|
||||
Renderer: 'a + advanced::Renderer,
|
||||
Message: 'a + Clone,
|
||||
{
|
||||
fn from(modal: Modal<'a, Message, Renderer>) -> Self {
|
||||
|
|
|
|||
|
|
@ -1,11 +0,0 @@
|
|||
[package]
|
||||
name = "modern_art"
|
||||
version = "0.1.0"
|
||||
authors = ["Bingus <shankern@protonmail.com>"]
|
||||
edition = "2021"
|
||||
publish = false
|
||||
|
||||
[dependencies]
|
||||
iced = { path = "../..", features = ["canvas", "tokio", "debug"] }
|
||||
rand = "0.8.5"
|
||||
env_logger = "0.9"
|
||||
|
|
@ -1,142 +0,0 @@
|
|||
use iced::widget::canvas::{
|
||||
self, gradient::Location, gradient::Position, Cache, Canvas, Cursor, Frame,
|
||||
Geometry, Gradient,
|
||||
};
|
||||
use iced::{
|
||||
executor, Application, Color, Command, Element, Length, Point, Rectangle,
|
||||
Renderer, Settings, Size, Theme,
|
||||
};
|
||||
use rand::{thread_rng, Rng};
|
||||
|
||||
fn main() -> iced::Result {
|
||||
env_logger::builder().format_timestamp(None).init();
|
||||
|
||||
ModernArt::run(Settings {
|
||||
antialiasing: true,
|
||||
..Settings::default()
|
||||
})
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
enum Message {}
|
||||
|
||||
struct ModernArt {
|
||||
cache: Cache,
|
||||
}
|
||||
|
||||
impl Application for ModernArt {
|
||||
type Executor = executor::Default;
|
||||
type Message = Message;
|
||||
type Theme = Theme;
|
||||
type Flags = ();
|
||||
|
||||
fn new(_flags: Self::Flags) -> (Self, Command<Self::Message>) {
|
||||
(
|
||||
ModernArt {
|
||||
cache: Default::default(),
|
||||
},
|
||||
Command::none(),
|
||||
)
|
||||
}
|
||||
|
||||
fn title(&self) -> String {
|
||||
String::from("Modern Art")
|
||||
}
|
||||
|
||||
fn update(&mut self, _message: Message) -> Command<Message> {
|
||||
Command::none()
|
||||
}
|
||||
|
||||
fn view(&self) -> Element<'_, Self::Message, Renderer<Self::Theme>> {
|
||||
Canvas::new(self)
|
||||
.width(Length::Fill)
|
||||
.height(Length::Fill)
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<Message> canvas::Program<Message> for ModernArt {
|
||||
type State = ();
|
||||
|
||||
fn draw(
|
||||
&self,
|
||||
_state: &Self::State,
|
||||
_theme: &Theme,
|
||||
bounds: Rectangle,
|
||||
_cursor: Cursor,
|
||||
) -> Vec<Geometry> {
|
||||
let geometry = self.cache.draw(bounds.size(), |frame| {
|
||||
let num_squares = thread_rng().gen_range(0..1200);
|
||||
|
||||
let mut i = 0;
|
||||
while i <= num_squares {
|
||||
generate_box(frame, bounds.size());
|
||||
i += 1;
|
||||
}
|
||||
});
|
||||
|
||||
vec![geometry]
|
||||
}
|
||||
}
|
||||
|
||||
fn random_direction() -> Location {
|
||||
match thread_rng().gen_range(0..8) {
|
||||
0 => Location::TopLeft,
|
||||
1 => Location::Top,
|
||||
2 => Location::TopRight,
|
||||
3 => Location::Right,
|
||||
4 => Location::BottomRight,
|
||||
5 => Location::Bottom,
|
||||
6 => Location::BottomLeft,
|
||||
7 => Location::Left,
|
||||
_ => Location::TopLeft,
|
||||
}
|
||||
}
|
||||
|
||||
fn generate_box(frame: &mut Frame, bounds: Size) -> bool {
|
||||
let solid = rand::random::<bool>();
|
||||
|
||||
let random_color = || -> Color {
|
||||
Color::from_rgb(
|
||||
thread_rng().gen_range(0.0..1.0),
|
||||
thread_rng().gen_range(0.0..1.0),
|
||||
thread_rng().gen_range(0.0..1.0),
|
||||
)
|
||||
};
|
||||
|
||||
let gradient = |top_left: Point, size: Size| -> Gradient {
|
||||
let mut builder = Gradient::linear(Position::Relative {
|
||||
top_left,
|
||||
size,
|
||||
start: random_direction(),
|
||||
end: random_direction(),
|
||||
});
|
||||
let stops = thread_rng().gen_range(1..15u32);
|
||||
|
||||
let mut i = 0;
|
||||
while i <= stops {
|
||||
builder = builder.add_stop(i as f32 / stops as f32, random_color());
|
||||
i += 1;
|
||||
}
|
||||
|
||||
builder.build().unwrap()
|
||||
};
|
||||
|
||||
let top_left = Point::new(
|
||||
thread_rng().gen_range(0.0..bounds.width),
|
||||
thread_rng().gen_range(0.0..bounds.height),
|
||||
);
|
||||
|
||||
let size = Size::new(
|
||||
thread_rng().gen_range(50.0..200.0),
|
||||
thread_rng().gen_range(50.0..200.0),
|
||||
);
|
||||
|
||||
if solid {
|
||||
frame.fill_rectangle(top_left, size, random_color());
|
||||
} else {
|
||||
frame.fill_rectangle(top_left, size, gradient(top_left, size));
|
||||
};
|
||||
|
||||
solid
|
||||
}
|
||||
|
|
@ -1,12 +1,13 @@
|
|||
//! This example shows how to use touch events in `Canvas` to draw
|
||||
//! a circle around each fingertip. This only works on touch-enabled
|
||||
//! computers like Microsoft Surface.
|
||||
use iced::mouse;
|
||||
use iced::widget::canvas::event;
|
||||
use iced::widget::canvas::stroke::{self, Stroke};
|
||||
use iced::widget::canvas::{self, Canvas, Cursor, Geometry};
|
||||
use iced::widget::canvas::{self, Canvas, Geometry};
|
||||
use iced::{
|
||||
executor, touch, window, Application, Color, Command, Element, Length,
|
||||
Point, Rectangle, Settings, Subscription, Theme,
|
||||
Point, Rectangle, Renderer, Settings, Subscription, Theme,
|
||||
};
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
|
@ -95,7 +96,7 @@ impl Application for Multitouch {
|
|||
}
|
||||
}
|
||||
|
||||
impl canvas::Program<Message> for State {
|
||||
impl canvas::Program<Message, Renderer> for State {
|
||||
type State = ();
|
||||
|
||||
fn update(
|
||||
|
|
@ -103,7 +104,7 @@ impl canvas::Program<Message> for State {
|
|||
_state: &mut Self::State,
|
||||
event: event::Event,
|
||||
_bounds: Rectangle,
|
||||
_cursor: Cursor,
|
||||
_cursor: mouse::Cursor,
|
||||
) -> (event::Status, Option<Message>) {
|
||||
match event {
|
||||
event::Event::Touch(touch_event) => match touch_event {
|
||||
|
|
@ -125,11 +126,12 @@ impl canvas::Program<Message> for State {
|
|||
fn draw(
|
||||
&self,
|
||||
_state: &Self::State,
|
||||
renderer: &Renderer,
|
||||
_theme: &Theme,
|
||||
bounds: Rectangle,
|
||||
_cursor: Cursor,
|
||||
_cursor: mouse::Cursor,
|
||||
) -> Vec<Geometry> {
|
||||
let fingerweb = self.cache.draw(bounds.size(), |frame| {
|
||||
let fingerweb = self.cache.draw(renderer, bounds.size(), |frame| {
|
||||
if self.fingers.len() < 2 {
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,4 @@ edition = "2021"
|
|||
publish = false
|
||||
|
||||
[dependencies]
|
||||
iced = { path = "../..", features = ["debug"] }
|
||||
iced_native = { path = "../../native" }
|
||||
iced_lazy = { path = "../../lazy" }
|
||||
iced = { path = "../..", features = ["debug", "lazy"] }
|
||||
|
|
|
|||
|
|
@ -1,14 +1,16 @@
|
|||
use iced::alignment::{self, Alignment};
|
||||
use iced::event::{self, Event};
|
||||
use iced::executor;
|
||||
use iced::keyboard;
|
||||
use iced::subscription;
|
||||
use iced::theme::{self, Theme};
|
||||
use iced::widget::pane_grid::{self, PaneGrid};
|
||||
use iced::widget::{button, column, container, row, scrollable, text};
|
||||
use iced::widget::{
|
||||
button, column, container, responsive, row, scrollable, text,
|
||||
};
|
||||
use iced::{
|
||||
Application, Color, Command, Element, Length, Settings, Size, Subscription,
|
||||
};
|
||||
use iced_lazy::responsive;
|
||||
use iced_native::{event, subscription, Event};
|
||||
|
||||
pub fn main() -> iced::Result {
|
||||
Example::run(Settings::default())
|
||||
|
|
@ -107,7 +109,7 @@ impl Application for Example {
|
|||
pane,
|
||||
target,
|
||||
}) => {
|
||||
self.panes.swap(&pane, &target);
|
||||
self.panes.drop(&pane, target);
|
||||
}
|
||||
Message::Dragged(_) => {}
|
||||
Message::TogglePin(pane) => {
|
||||
|
|
@ -253,6 +255,7 @@ fn handle_hotkey(key_code: keyboard::KeyCode) -> Option<Message> {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
struct Pane {
|
||||
id: usize,
|
||||
pub is_pinned: bool,
|
||||
|
|
@ -296,7 +299,7 @@ fn view_content<'a>(
|
|||
)
|
||||
]
|
||||
.spacing(5)
|
||||
.max_width(150);
|
||||
.max_width(160);
|
||||
|
||||
if total_panes > 1 && !is_pinned {
|
||||
controls = controls.push(
|
||||
|
|
|
|||
|
|
@ -61,8 +61,9 @@ impl Sandbox for Example {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
|
||||
pub enum Language {
|
||||
#[default]
|
||||
Rust,
|
||||
Elm,
|
||||
Ruby,
|
||||
|
|
@ -84,12 +85,6 @@ impl Language {
|
|||
];
|
||||
}
|
||||
|
||||
impl Default for Language {
|
||||
fn default() -> Language {
|
||||
Language::Rust
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Language {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(
|
||||
|
|
|
|||
|
|
@ -49,13 +49,11 @@ impl Sandbox for QRGenerator {
|
|||
.size(70)
|
||||
.style(Color::from([0.5, 0.5, 0.5]));
|
||||
|
||||
let input = text_input(
|
||||
"Type the data of your QR code here...",
|
||||
&self.data,
|
||||
Message::DataChanged,
|
||||
)
|
||||
.size(30)
|
||||
.padding(15);
|
||||
let input =
|
||||
text_input("Type the data of your QR code here...", &self.data)
|
||||
.on_input(Message::DataChanged)
|
||||
.size(30)
|
||||
.padding(15);
|
||||
|
||||
let mut content = column![title, input]
|
||||
.width(700)
|
||||
|
|
|
|||
11
examples/screenshot/Cargo.toml
Normal file
11
examples/screenshot/Cargo.toml
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
[package]
|
||||
name = "screenshot"
|
||||
version = "0.1.0"
|
||||
authors = ["Bingus <shankern@protonmail.com>"]
|
||||
edition = "2021"
|
||||
publish = false
|
||||
|
||||
[dependencies]
|
||||
iced = { path = "../..", features = ["debug", "image", "advanced"] }
|
||||
image = { version = "0.24.6", features = ["png"]}
|
||||
env_logger = "0.10.0"
|
||||
320
examples/screenshot/src/main.rs
Normal file
320
examples/screenshot/src/main.rs
Normal file
|
|
@ -0,0 +1,320 @@
|
|||
use iced::alignment;
|
||||
use iced::keyboard::KeyCode;
|
||||
use iced::theme::{Button, Container};
|
||||
use iced::widget::{button, column, container, image, row, text, text_input};
|
||||
use iced::window::screenshot::{self, Screenshot};
|
||||
use iced::{
|
||||
event, executor, keyboard, subscription, Alignment, Application, Command,
|
||||
ContentFit, Element, Event, Length, Rectangle, Renderer, Subscription,
|
||||
Theme,
|
||||
};
|
||||
|
||||
use ::image as img;
|
||||
use ::image::ColorType;
|
||||
|
||||
fn main() -> iced::Result {
|
||||
env_logger::builder().format_timestamp(None).init();
|
||||
|
||||
Example::run(iced::Settings::default())
|
||||
}
|
||||
|
||||
struct Example {
|
||||
screenshot: Option<Screenshot>,
|
||||
saved_png_path: Option<Result<String, PngError>>,
|
||||
png_saving: bool,
|
||||
crop_error: Option<screenshot::CropError>,
|
||||
x_input_value: Option<u32>,
|
||||
y_input_value: Option<u32>,
|
||||
width_input_value: Option<u32>,
|
||||
height_input_value: Option<u32>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
enum Message {
|
||||
Crop,
|
||||
Screenshot,
|
||||
ScreenshotData(Screenshot),
|
||||
Png,
|
||||
PngSaved(Result<String, PngError>),
|
||||
XInputChanged(Option<u32>),
|
||||
YInputChanged(Option<u32>),
|
||||
WidthInputChanged(Option<u32>),
|
||||
HeightInputChanged(Option<u32>),
|
||||
}
|
||||
|
||||
impl Application for Example {
|
||||
type Executor = executor::Default;
|
||||
type Message = Message;
|
||||
type Theme = Theme;
|
||||
type Flags = ();
|
||||
|
||||
fn new(_flags: Self::Flags) -> (Self, Command<Self::Message>) {
|
||||
(
|
||||
Example {
|
||||
screenshot: None,
|
||||
saved_png_path: None,
|
||||
png_saving: false,
|
||||
crop_error: None,
|
||||
x_input_value: None,
|
||||
y_input_value: None,
|
||||
width_input_value: None,
|
||||
height_input_value: None,
|
||||
},
|
||||
Command::none(),
|
||||
)
|
||||
}
|
||||
|
||||
fn title(&self) -> String {
|
||||
"Screenshot".to_string()
|
||||
}
|
||||
|
||||
fn update(&mut self, message: Self::Message) -> Command<Self::Message> {
|
||||
match message {
|
||||
Message::Screenshot => {
|
||||
return iced::window::screenshot(Message::ScreenshotData);
|
||||
}
|
||||
Message::ScreenshotData(screenshot) => {
|
||||
self.screenshot = Some(screenshot);
|
||||
}
|
||||
Message::Png => {
|
||||
if let Some(screenshot) = &self.screenshot {
|
||||
self.png_saving = true;
|
||||
|
||||
return Command::perform(
|
||||
save_to_png(screenshot.clone()),
|
||||
Message::PngSaved,
|
||||
);
|
||||
}
|
||||
}
|
||||
Message::PngSaved(res) => {
|
||||
self.png_saving = false;
|
||||
self.saved_png_path = Some(res);
|
||||
}
|
||||
Message::XInputChanged(new_value) => {
|
||||
self.x_input_value = new_value;
|
||||
}
|
||||
Message::YInputChanged(new_value) => {
|
||||
self.y_input_value = new_value;
|
||||
}
|
||||
Message::WidthInputChanged(new_value) => {
|
||||
self.width_input_value = new_value;
|
||||
}
|
||||
Message::HeightInputChanged(new_value) => {
|
||||
self.height_input_value = new_value;
|
||||
}
|
||||
Message::Crop => {
|
||||
if let Some(screenshot) = &self.screenshot {
|
||||
let cropped = screenshot.crop(Rectangle::<u32> {
|
||||
x: self.x_input_value.unwrap_or(0),
|
||||
y: self.y_input_value.unwrap_or(0),
|
||||
width: self.width_input_value.unwrap_or(0),
|
||||
height: self.height_input_value.unwrap_or(0),
|
||||
});
|
||||
|
||||
match cropped {
|
||||
Ok(screenshot) => {
|
||||
self.screenshot = Some(screenshot);
|
||||
self.crop_error = None;
|
||||
}
|
||||
Err(crop_error) => {
|
||||
self.crop_error = Some(crop_error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Command::none()
|
||||
}
|
||||
|
||||
fn view(&self) -> Element<'_, Self::Message, Renderer<Self::Theme>> {
|
||||
let image: Element<Message> = if let Some(screenshot) = &self.screenshot
|
||||
{
|
||||
image(image::Handle::from_pixels(
|
||||
screenshot.size.width,
|
||||
screenshot.size.height,
|
||||
screenshot.clone(),
|
||||
))
|
||||
.content_fit(ContentFit::Contain)
|
||||
.width(Length::Fill)
|
||||
.height(Length::Fill)
|
||||
.into()
|
||||
} else {
|
||||
text("Press the button to take a screenshot!").into()
|
||||
};
|
||||
|
||||
let image = container(image)
|
||||
.padding(10)
|
||||
.style(Container::Box)
|
||||
.width(Length::FillPortion(2))
|
||||
.height(Length::Fill)
|
||||
.center_x()
|
||||
.center_y();
|
||||
|
||||
let crop_origin_controls = row![
|
||||
text("X:")
|
||||
.vertical_alignment(alignment::Vertical::Center)
|
||||
.width(30),
|
||||
numeric_input("0", self.x_input_value).map(Message::XInputChanged),
|
||||
text("Y:")
|
||||
.vertical_alignment(alignment::Vertical::Center)
|
||||
.width(30),
|
||||
numeric_input("0", self.y_input_value).map(Message::YInputChanged)
|
||||
]
|
||||
.spacing(10)
|
||||
.align_items(Alignment::Center);
|
||||
|
||||
let crop_dimension_controls = row![
|
||||
text("W:")
|
||||
.vertical_alignment(alignment::Vertical::Center)
|
||||
.width(30),
|
||||
numeric_input("0", self.width_input_value)
|
||||
.map(Message::WidthInputChanged),
|
||||
text("H:")
|
||||
.vertical_alignment(alignment::Vertical::Center)
|
||||
.width(30),
|
||||
numeric_input("0", self.height_input_value)
|
||||
.map(Message::HeightInputChanged)
|
||||
]
|
||||
.spacing(10)
|
||||
.align_items(Alignment::Center);
|
||||
|
||||
let mut crop_controls =
|
||||
column![crop_origin_controls, crop_dimension_controls]
|
||||
.spacing(10)
|
||||
.align_items(Alignment::Center);
|
||||
|
||||
if let Some(crop_error) = &self.crop_error {
|
||||
crop_controls = crop_controls
|
||||
.push(text(format!("Crop error! \n{}", crop_error)));
|
||||
}
|
||||
|
||||
let mut controls = column![
|
||||
column![
|
||||
button(centered_text("Screenshot!"))
|
||||
.padding([10, 20, 10, 20])
|
||||
.width(Length::Fill)
|
||||
.on_press(Message::Screenshot),
|
||||
if !self.png_saving {
|
||||
button(centered_text("Save as png")).on_press_maybe(
|
||||
self.screenshot.is_some().then(|| Message::Png),
|
||||
)
|
||||
} else {
|
||||
button(centered_text("Saving...")).style(Button::Secondary)
|
||||
}
|
||||
.style(Button::Secondary)
|
||||
.padding([10, 20, 10, 20])
|
||||
.width(Length::Fill)
|
||||
]
|
||||
.spacing(10),
|
||||
column![
|
||||
crop_controls,
|
||||
button(centered_text("Crop"))
|
||||
.on_press(Message::Crop)
|
||||
.style(Button::Destructive)
|
||||
.padding([10, 20, 10, 20])
|
||||
.width(Length::Fill),
|
||||
]
|
||||
.spacing(10)
|
||||
.align_items(Alignment::Center),
|
||||
]
|
||||
.spacing(40);
|
||||
|
||||
if let Some(png_result) = &self.saved_png_path {
|
||||
let msg = match png_result {
|
||||
Ok(path) => format!("Png saved as: {:?}!", path),
|
||||
Err(msg) => {
|
||||
format!("Png could not be saved due to:\n{:?}", msg)
|
||||
}
|
||||
};
|
||||
|
||||
controls = controls.push(text(msg));
|
||||
}
|
||||
|
||||
let side_content = container(controls)
|
||||
.align_x(alignment::Horizontal::Center)
|
||||
.width(Length::FillPortion(1))
|
||||
.height(Length::Fill)
|
||||
.center_y()
|
||||
.center_x();
|
||||
|
||||
let content = row![side_content, image]
|
||||
.spacing(10)
|
||||
.width(Length::Fill)
|
||||
.height(Length::Fill)
|
||||
.align_items(Alignment::Center);
|
||||
|
||||
container(content)
|
||||
.width(Length::Fill)
|
||||
.height(Length::Fill)
|
||||
.padding(10)
|
||||
.center_x()
|
||||
.center_y()
|
||||
.into()
|
||||
}
|
||||
|
||||
fn subscription(&self) -> Subscription<Self::Message> {
|
||||
subscription::events_with(|event, status| {
|
||||
if let event::Status::Captured = status {
|
||||
return None;
|
||||
}
|
||||
|
||||
if let Event::Keyboard(keyboard::Event::KeyPressed {
|
||||
key_code: KeyCode::F5,
|
||||
..
|
||||
}) = event
|
||||
{
|
||||
Some(Message::Screenshot)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
async fn save_to_png(screenshot: Screenshot) -> Result<String, PngError> {
|
||||
let path = "screenshot.png".to_string();
|
||||
img::save_buffer(
|
||||
&path,
|
||||
&screenshot.bytes,
|
||||
screenshot.size.width,
|
||||
screenshot.size.height,
|
||||
ColorType::Rgba8,
|
||||
)
|
||||
.map(|_| path)
|
||||
.map_err(|err| PngError(format!("{:?}", err)))
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
struct PngError(String);
|
||||
|
||||
fn numeric_input(
|
||||
placeholder: &str,
|
||||
value: Option<u32>,
|
||||
) -> Element<'_, Option<u32>> {
|
||||
text_input(
|
||||
placeholder,
|
||||
&value
|
||||
.as_ref()
|
||||
.map(ToString::to_string)
|
||||
.unwrap_or_else(String::new),
|
||||
)
|
||||
.on_input(move |text| {
|
||||
if text.is_empty() {
|
||||
None
|
||||
} else if let Ok(new_value) = text.parse() {
|
||||
Some(new_value)
|
||||
} else {
|
||||
value
|
||||
}
|
||||
})
|
||||
.width(40)
|
||||
.into()
|
||||
}
|
||||
|
||||
fn centered_text(content: &str) -> Element<'_, Message> {
|
||||
text(content)
|
||||
.width(Length::Fill)
|
||||
.horizontal_alignment(alignment::Horizontal::Center)
|
||||
.into()
|
||||
}
|
||||
|
|
@ -5,6 +5,7 @@ use iced::widget::{
|
|||
};
|
||||
use iced::{executor, theme, Alignment, Color};
|
||||
use iced::{Application, Command, Element, Length, Settings, Theme};
|
||||
|
||||
use once_cell::sync::Lazy;
|
||||
|
||||
static SCROLLABLE_ID: Lazy<scrollable::Id> = Lazy::new(scrollable::Id::unique);
|
||||
|
|
@ -19,6 +20,7 @@ struct ScrollableDemo {
|
|||
scrollbar_margin: u16,
|
||||
scroller_width: u16,
|
||||
current_scroll_offset: scrollable::RelativeOffset,
|
||||
alignment: scrollable::Alignment,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Copy)]
|
||||
|
|
@ -31,12 +33,13 @@ enum Direction {
|
|||
#[derive(Debug, Clone)]
|
||||
enum Message {
|
||||
SwitchDirection(Direction),
|
||||
AlignmentChanged(scrollable::Alignment),
|
||||
ScrollbarWidthChanged(u16),
|
||||
ScrollbarMarginChanged(u16),
|
||||
ScrollerWidthChanged(u16),
|
||||
ScrollToBeginning,
|
||||
ScrollToEnd,
|
||||
Scrolled(scrollable::RelativeOffset),
|
||||
Scrolled(scrollable::Viewport),
|
||||
}
|
||||
|
||||
impl Application for ScrollableDemo {
|
||||
|
|
@ -53,6 +56,7 @@ impl Application for ScrollableDemo {
|
|||
scrollbar_margin: 0,
|
||||
scroller_width: 10,
|
||||
current_scroll_offset: scrollable::RelativeOffset::START,
|
||||
alignment: scrollable::Alignment::Start,
|
||||
},
|
||||
Command::none(),
|
||||
)
|
||||
|
|
@ -73,6 +77,15 @@ impl Application for ScrollableDemo {
|
|||
self.current_scroll_offset,
|
||||
)
|
||||
}
|
||||
Message::AlignmentChanged(alignment) => {
|
||||
self.current_scroll_offset = scrollable::RelativeOffset::START;
|
||||
self.alignment = alignment;
|
||||
|
||||
scrollable::snap_to(
|
||||
SCROLLABLE_ID.clone(),
|
||||
self.current_scroll_offset,
|
||||
)
|
||||
}
|
||||
Message::ScrollbarWidthChanged(width) => {
|
||||
self.scrollbar_width = width;
|
||||
|
||||
|
|
@ -104,8 +117,8 @@ impl Application for ScrollableDemo {
|
|||
self.current_scroll_offset,
|
||||
)
|
||||
}
|
||||
Message::Scrolled(offset) => {
|
||||
self.current_scroll_offset = offset;
|
||||
Message::Scrolled(viewport) => {
|
||||
self.current_scroll_offset = viewport.relative_offset();
|
||||
|
||||
Command::none()
|
||||
}
|
||||
|
|
@ -164,10 +177,33 @@ impl Application for ScrollableDemo {
|
|||
.spacing(10)
|
||||
.width(Length::Fill);
|
||||
|
||||
let scroll_controls =
|
||||
row![scroll_slider_controls, scroll_orientation_controls]
|
||||
.spacing(20)
|
||||
.width(Length::Fill);
|
||||
let scroll_alignment_controls = column(vec![
|
||||
text("Scrollable alignment:").into(),
|
||||
radio(
|
||||
"Start",
|
||||
scrollable::Alignment::Start,
|
||||
Some(self.alignment),
|
||||
Message::AlignmentChanged,
|
||||
)
|
||||
.into(),
|
||||
radio(
|
||||
"End",
|
||||
scrollable::Alignment::End,
|
||||
Some(self.alignment),
|
||||
Message::AlignmentChanged,
|
||||
)
|
||||
.into(),
|
||||
])
|
||||
.spacing(10)
|
||||
.width(Length::Fill);
|
||||
|
||||
let scroll_controls = row![
|
||||
scroll_slider_controls,
|
||||
scroll_orientation_controls,
|
||||
scroll_alignment_controls
|
||||
]
|
||||
.spacing(20)
|
||||
.width(Length::Fill);
|
||||
|
||||
let scroll_to_end_button = || {
|
||||
button("Scroll to end")
|
||||
|
|
@ -199,12 +235,13 @@ impl Application for ScrollableDemo {
|
|||
.spacing(40),
|
||||
)
|
||||
.height(Length::Fill)
|
||||
.vertical_scroll(
|
||||
.direction(scrollable::Direction::Vertical(
|
||||
Properties::new()
|
||||
.width(self.scrollbar_width)
|
||||
.margin(self.scrollbar_margin)
|
||||
.scroller_width(self.scroller_width),
|
||||
)
|
||||
.scroller_width(self.scroller_width)
|
||||
.alignment(self.alignment),
|
||||
))
|
||||
.id(SCROLLABLE_ID.clone())
|
||||
.on_scroll(Message::Scrolled),
|
||||
Direction::Horizontal => scrollable(
|
||||
|
|
@ -223,12 +260,13 @@ impl Application for ScrollableDemo {
|
|||
.spacing(40),
|
||||
)
|
||||
.height(Length::Fill)
|
||||
.horizontal_scroll(
|
||||
.direction(scrollable::Direction::Horizontal(
|
||||
Properties::new()
|
||||
.width(self.scrollbar_width)
|
||||
.margin(self.scrollbar_margin)
|
||||
.scroller_width(self.scroller_width),
|
||||
)
|
||||
.scroller_width(self.scroller_width)
|
||||
.alignment(self.alignment),
|
||||
))
|
||||
.style(theme::Scrollable::custom(ScrollbarCustomStyle))
|
||||
.id(SCROLLABLE_ID.clone())
|
||||
.on_scroll(Message::Scrolled),
|
||||
|
|
@ -264,18 +302,18 @@ impl Application for ScrollableDemo {
|
|||
.spacing(40),
|
||||
)
|
||||
.height(Length::Fill)
|
||||
.vertical_scroll(
|
||||
Properties::new()
|
||||
.direction({
|
||||
let properties = Properties::new()
|
||||
.width(self.scrollbar_width)
|
||||
.margin(self.scrollbar_margin)
|
||||
.scroller_width(self.scroller_width),
|
||||
)
|
||||
.horizontal_scroll(
|
||||
Properties::new()
|
||||
.width(self.scrollbar_width)
|
||||
.margin(self.scrollbar_margin)
|
||||
.scroller_width(self.scroller_width),
|
||||
)
|
||||
.scroller_width(self.scroller_width)
|
||||
.alignment(self.alignment);
|
||||
|
||||
scrollable::Direction::Both {
|
||||
horizontal: properties,
|
||||
vertical: properties,
|
||||
}
|
||||
})
|
||||
.style(theme::Scrollable::Custom(Box::new(
|
||||
ScrollbarCustomStyle,
|
||||
)))
|
||||
|
|
@ -289,18 +327,13 @@ impl Application for ScrollableDemo {
|
|||
}
|
||||
Direction::Horizontal => {
|
||||
progress_bar(0.0..=1.0, self.current_scroll_offset.x)
|
||||
.style(theme::ProgressBar::Custom(Box::new(
|
||||
ProgressBarCustomStyle,
|
||||
)))
|
||||
.style(progress_bar_custom_style)
|
||||
.into()
|
||||
}
|
||||
Direction::Multi => column![
|
||||
progress_bar(0.0..=1.0, self.current_scroll_offset.y),
|
||||
progress_bar(0.0..=1.0, self.current_scroll_offset.x).style(
|
||||
theme::ProgressBar::Custom(Box::new(
|
||||
ProgressBarCustomStyle,
|
||||
))
|
||||
)
|
||||
progress_bar(0.0..=1.0, self.current_scroll_offset.x)
|
||||
.style(progress_bar_custom_style)
|
||||
]
|
||||
.spacing(10)
|
||||
.into(),
|
||||
|
|
@ -338,36 +371,44 @@ impl scrollable::StyleSheet for ScrollbarCustomStyle {
|
|||
style.active(&theme::Scrollable::Default)
|
||||
}
|
||||
|
||||
fn hovered(&self, style: &Self::Style) -> Scrollbar {
|
||||
style.hovered(&theme::Scrollable::Default)
|
||||
fn hovered(
|
||||
&self,
|
||||
style: &Self::Style,
|
||||
is_mouse_over_scrollbar: bool,
|
||||
) -> Scrollbar {
|
||||
style.hovered(&theme::Scrollable::Default, is_mouse_over_scrollbar)
|
||||
}
|
||||
|
||||
fn hovered_horizontal(&self, style: &Self::Style) -> Scrollbar {
|
||||
Scrollbar {
|
||||
background: style.active(&theme::Scrollable::default()).background,
|
||||
border_radius: 0.0,
|
||||
border_width: 0.0,
|
||||
border_color: Default::default(),
|
||||
scroller: Scroller {
|
||||
color: Color::from_rgb8(250, 85, 134),
|
||||
border_radius: 0.0,
|
||||
fn hovered_horizontal(
|
||||
&self,
|
||||
style: &Self::Style,
|
||||
is_mouse_over_scrollbar: bool,
|
||||
) -> Scrollbar {
|
||||
if is_mouse_over_scrollbar {
|
||||
Scrollbar {
|
||||
background: style
|
||||
.active(&theme::Scrollable::default())
|
||||
.background,
|
||||
border_radius: 0.0.into(),
|
||||
border_width: 0.0,
|
||||
border_color: Default::default(),
|
||||
},
|
||||
scroller: Scroller {
|
||||
color: Color::from_rgb8(250, 85, 134),
|
||||
border_radius: 0.0.into(),
|
||||
border_width: 0.0,
|
||||
border_color: Default::default(),
|
||||
},
|
||||
}
|
||||
} else {
|
||||
self.active(style)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct ProgressBarCustomStyle;
|
||||
|
||||
impl progress_bar::StyleSheet for ProgressBarCustomStyle {
|
||||
type Style = Theme;
|
||||
|
||||
fn appearance(&self, style: &Self::Style) -> progress_bar::Appearance {
|
||||
progress_bar::Appearance {
|
||||
background: style.extended_palette().background.strong.color.into(),
|
||||
bar: Color::from_rgb8(250, 85, 134).into(),
|
||||
border_radius: 0.0,
|
||||
}
|
||||
fn progress_bar_custom_style(theme: &Theme) -> progress_bar::Appearance {
|
||||
progress_bar::Appearance {
|
||||
background: theme.extended_palette().background.strong.color.into(),
|
||||
bar: Color::from_rgb8(250, 85, 134).into(),
|
||||
border_radius: 0.0.into(),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,13 @@
|
|||
use std::fmt::Debug;
|
||||
|
||||
use iced::executor;
|
||||
use iced::mouse;
|
||||
use iced::widget::canvas::event::{self, Event};
|
||||
use iced::widget::canvas::{self, Canvas};
|
||||
use iced::widget::{column, row, slider, text};
|
||||
use iced::{
|
||||
Application, Color, Command, Length, Point, Rectangle, Settings, Size,
|
||||
Theme,
|
||||
Application, Color, Command, Length, Point, Rectangle, Renderer, Settings,
|
||||
Size, Theme,
|
||||
};
|
||||
|
||||
use rand::Rng;
|
||||
|
|
@ -105,14 +106,14 @@ impl canvas::Program<Message> for SierpinskiGraph {
|
|||
_state: &mut Self::State,
|
||||
event: Event,
|
||||
bounds: Rectangle,
|
||||
cursor: canvas::Cursor,
|
||||
cursor: mouse::Cursor,
|
||||
) -> (event::Status, Option<Message>) {
|
||||
let cursor_position =
|
||||
if let Some(position) = cursor.position_in(&bounds) {
|
||||
position
|
||||
} else {
|
||||
return (event::Status::Ignored, None);
|
||||
};
|
||||
let cursor_position = if let Some(position) = cursor.position_in(bounds)
|
||||
{
|
||||
position
|
||||
} else {
|
||||
return (event::Status::Ignored, None);
|
||||
};
|
||||
|
||||
match event {
|
||||
Event::Mouse(mouse_event) => {
|
||||
|
|
@ -134,11 +135,12 @@ impl canvas::Program<Message> for SierpinskiGraph {
|
|||
fn draw(
|
||||
&self,
|
||||
_state: &Self::State,
|
||||
renderer: &Renderer,
|
||||
_theme: &Theme,
|
||||
bounds: Rectangle,
|
||||
_cursor: canvas::Cursor,
|
||||
_cursor: mouse::Cursor,
|
||||
) -> Vec<canvas::Geometry> {
|
||||
let geom = self.cache.draw(bounds.size(), |frame| {
|
||||
let geom = self.cache.draw(renderer, bounds.size(), |frame| {
|
||||
frame.stroke(
|
||||
&canvas::Path::rectangle(Point::ORIGIN, frame.size()),
|
||||
canvas::Stroke::default(),
|
||||
|
|
|
|||
|
|
@ -7,4 +7,5 @@ publish = false
|
|||
|
||||
[dependencies]
|
||||
iced = { path = "../..", features = ["canvas", "tokio", "debug"] }
|
||||
env_logger = "0.10.0"
|
||||
rand = "0.8.3"
|
||||
|
|
|
|||
|
|
@ -8,20 +8,23 @@
|
|||
//! [1]: https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial/Basic_animations#An_animated_solar_system
|
||||
use iced::application;
|
||||
use iced::executor;
|
||||
use iced::mouse;
|
||||
use iced::theme::{self, Theme};
|
||||
use iced::widget::canvas;
|
||||
use iced::widget::canvas::gradient::{self, Gradient};
|
||||
use iced::widget::canvas::gradient;
|
||||
use iced::widget::canvas::stroke::{self, Stroke};
|
||||
use iced::widget::canvas::{Cursor, Path};
|
||||
use iced::widget::canvas::Path;
|
||||
use iced::window;
|
||||
use iced::{
|
||||
Application, Color, Command, Element, Length, Point, Rectangle, Settings,
|
||||
Size, Subscription, Vector,
|
||||
Application, Color, Command, Element, Length, Point, Rectangle, Renderer,
|
||||
Settings, Size, Subscription, Vector,
|
||||
};
|
||||
|
||||
use std::time::Instant;
|
||||
|
||||
pub fn main() -> iced::Result {
|
||||
env_logger::builder().format_timestamp(None).init();
|
||||
|
||||
SolarSystem::run(Settings {
|
||||
antialiasing: true,
|
||||
..Settings::default()
|
||||
|
|
@ -156,24 +159,26 @@ impl<Message> canvas::Program<Message> for State {
|
|||
fn draw(
|
||||
&self,
|
||||
_state: &Self::State,
|
||||
renderer: &Renderer,
|
||||
_theme: &Theme,
|
||||
bounds: Rectangle,
|
||||
_cursor: Cursor,
|
||||
_cursor: mouse::Cursor,
|
||||
) -> Vec<canvas::Geometry> {
|
||||
use std::f32::consts::PI;
|
||||
|
||||
let background = self.space_cache.draw(bounds.size(), |frame| {
|
||||
let stars = Path::new(|path| {
|
||||
for (p, size) in &self.stars {
|
||||
path.rectangle(*p, Size::new(*size, *size));
|
||||
}
|
||||
let background =
|
||||
self.space_cache.draw(renderer, bounds.size(), |frame| {
|
||||
let stars = Path::new(|path| {
|
||||
for (p, size) in &self.stars {
|
||||
path.rectangle(*p, Size::new(*size, *size));
|
||||
}
|
||||
});
|
||||
|
||||
frame.translate(frame.center() - Point::ORIGIN);
|
||||
frame.fill(&stars, Color::WHITE);
|
||||
});
|
||||
|
||||
frame.translate(frame.center() - Point::ORIGIN);
|
||||
frame.fill(&stars, Color::WHITE);
|
||||
});
|
||||
|
||||
let system = self.system_cache.draw(bounds.size(), |frame| {
|
||||
let system = self.system_cache.draw(renderer, bounds.size(), |frame| {
|
||||
let center = frame.center();
|
||||
|
||||
let sun = Path::circle(center, Self::SUN_RADIUS);
|
||||
|
|
@ -206,15 +211,12 @@ impl<Message> canvas::Program<Message> for State {
|
|||
|
||||
let earth = Path::circle(Point::ORIGIN, Self::EARTH_RADIUS);
|
||||
|
||||
let earth_fill =
|
||||
Gradient::linear(gradient::Position::Absolute {
|
||||
start: Point::new(-Self::EARTH_RADIUS, 0.0),
|
||||
end: Point::new(Self::EARTH_RADIUS, 0.0),
|
||||
})
|
||||
.add_stop(0.2, Color::from_rgb(0.15, 0.50, 1.0))
|
||||
.add_stop(0.8, Color::from_rgb(0.0, 0.20, 0.47))
|
||||
.build()
|
||||
.expect("Build Earth fill gradient");
|
||||
let earth_fill = gradient::Linear::new(
|
||||
Point::new(-Self::EARTH_RADIUS, 0.0),
|
||||
Point::new(Self::EARTH_RADIUS, 0.0),
|
||||
)
|
||||
.add_stop(0.2, Color::from_rgb(0.15, 0.50, 1.0))
|
||||
.add_stop(0.8, Color::from_rgb(0.0, 0.20, 0.47));
|
||||
|
||||
frame.fill(&earth, earth_fill);
|
||||
|
||||
|
|
|
|||
|
|
@ -90,13 +90,10 @@ impl Sandbox for Styling {
|
|||
},
|
||||
);
|
||||
|
||||
let text_input = text_input(
|
||||
"Type something...",
|
||||
&self.input_value,
|
||||
Message::InputChanged,
|
||||
)
|
||||
.padding(10)
|
||||
.size(20);
|
||||
let text_input = text_input("Type something...", &self.input_value)
|
||||
.on_input(Message::InputChanged)
|
||||
.padding(10)
|
||||
.size(20);
|
||||
|
||||
let button = button("Submit")
|
||||
.padding(10)
|
||||
|
|
@ -130,7 +127,9 @@ impl Sandbox for Styling {
|
|||
let content = column![
|
||||
choose_theme,
|
||||
horizontal_rule(38),
|
||||
row![text_input, button].spacing(10),
|
||||
row![text_input, button]
|
||||
.spacing(10)
|
||||
.align_items(Alignment::Center),
|
||||
slider,
|
||||
progress_bar,
|
||||
row![
|
||||
|
|
|
|||
|
|
@ -6,5 +6,4 @@ edition = "2021"
|
|||
publish = false
|
||||
|
||||
[dependencies]
|
||||
iced = { path = "../..", features = [] }
|
||||
iced_native = { path = "../../native" }
|
||||
iced = { path = "../..", features = ["advanced"] }
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
use iced::executor;
|
||||
use iced::keyboard;
|
||||
use iced::subscription::{self, Subscription};
|
||||
use iced::widget::{
|
||||
self, button, column, container, pick_list, row, slider, text, text_input,
|
||||
};
|
||||
use iced::{
|
||||
executor, keyboard, subscription, Alignment, Application, Command, Element,
|
||||
Event, Length, Settings, Subscription,
|
||||
};
|
||||
use iced::{Alignment, Application, Command, Element, Event, Length, Settings};
|
||||
|
||||
use toast::{Status, Toast};
|
||||
|
||||
|
|
@ -119,13 +119,15 @@ impl Application for App {
|
|||
column![
|
||||
subtitle(
|
||||
"Title",
|
||||
text_input("", &self.editing.title, Message::Title)
|
||||
text_input("", &self.editing.title)
|
||||
.on_input(Message::Title)
|
||||
.on_submit(Message::Add)
|
||||
.into()
|
||||
),
|
||||
subtitle(
|
||||
"Message",
|
||||
text_input("", &self.editing.body, Message::Body)
|
||||
text_input("", &self.editing.body)
|
||||
.on_input(Message::Body)
|
||||
.on_submit(Message::Add)
|
||||
.into()
|
||||
),
|
||||
|
|
@ -176,17 +178,23 @@ mod toast {
|
|||
use std::fmt;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use iced::advanced;
|
||||
use iced::advanced::layout::{self, Layout};
|
||||
use iced::advanced::overlay;
|
||||
use iced::advanced::renderer;
|
||||
use iced::advanced::widget::{self, Operation, Tree};
|
||||
use iced::advanced::{Clipboard, Shell, Widget};
|
||||
use iced::event::{self, Event};
|
||||
use iced::mouse;
|
||||
use iced::theme;
|
||||
use iced::widget::{
|
||||
button, column, container, horizontal_rule, horizontal_space, row, text,
|
||||
};
|
||||
use iced::window;
|
||||
use iced::{
|
||||
Alignment, Element, Length, Point, Rectangle, Renderer, Size, Theme,
|
||||
Vector,
|
||||
};
|
||||
use iced_native::widget::{tree, Operation, Tree};
|
||||
use iced_native::{event, layout, mouse, overlay, renderer, window};
|
||||
use iced_native::{Clipboard, Event, Layout, Shell, Widget};
|
||||
|
||||
pub const DEFAULT_TIMEOUT: u64 = 5;
|
||||
|
||||
|
|
@ -218,7 +226,7 @@ mod toast {
|
|||
};
|
||||
|
||||
container::Appearance {
|
||||
background: pair.color.into(),
|
||||
background: Some(pair.color.into()),
|
||||
text_color: pair.text.into(),
|
||||
..Default::default()
|
||||
}
|
||||
|
|
@ -324,13 +332,13 @@ mod toast {
|
|||
self.content.as_widget().layout(renderer, limits)
|
||||
}
|
||||
|
||||
fn tag(&self) -> tree::Tag {
|
||||
fn tag(&self) -> widget::tree::Tag {
|
||||
struct Marker(Vec<Instant>);
|
||||
iced_native::widget::tree::Tag::of::<Marker>()
|
||||
widget::tree::Tag::of::<Marker>()
|
||||
}
|
||||
|
||||
fn state(&self) -> tree::State {
|
||||
iced_native::widget::tree::State::new(Vec::<Option<Instant>>::new())
|
||||
fn state(&self) -> widget::tree::State {
|
||||
widget::tree::State::new(Vec::<Option<Instant>>::new())
|
||||
}
|
||||
|
||||
fn children(&self) -> Vec<Tree> {
|
||||
|
|
@ -388,7 +396,7 @@ mod toast {
|
|||
state: &mut Tree,
|
||||
event: Event,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
cursor: mouse::Cursor,
|
||||
renderer: &Renderer,
|
||||
clipboard: &mut dyn Clipboard,
|
||||
shell: &mut Shell<'_, Message>,
|
||||
|
|
@ -397,7 +405,7 @@ mod toast {
|
|||
&mut state.children[0],
|
||||
event,
|
||||
layout,
|
||||
cursor_position,
|
||||
cursor,
|
||||
renderer,
|
||||
clipboard,
|
||||
shell,
|
||||
|
|
@ -411,7 +419,7 @@ mod toast {
|
|||
theme: &Theme,
|
||||
style: &renderer::Style,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
cursor: mouse::Cursor,
|
||||
viewport: &Rectangle,
|
||||
) {
|
||||
self.content.as_widget().draw(
|
||||
|
|
@ -420,7 +428,7 @@ mod toast {
|
|||
theme,
|
||||
style,
|
||||
layout,
|
||||
cursor_position,
|
||||
cursor,
|
||||
viewport,
|
||||
);
|
||||
}
|
||||
|
|
@ -429,14 +437,14 @@ mod toast {
|
|||
&self,
|
||||
state: &Tree,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
cursor: mouse::Cursor,
|
||||
viewport: &Rectangle,
|
||||
renderer: &Renderer,
|
||||
) -> mouse::Interaction {
|
||||
self.content.as_widget().mouse_interaction(
|
||||
&state.children[0],
|
||||
layout,
|
||||
cursor_position,
|
||||
cursor,
|
||||
viewport,
|
||||
renderer,
|
||||
)
|
||||
|
|
@ -515,7 +523,7 @@ mod toast {
|
|||
&mut self,
|
||||
event: Event,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
cursor: mouse::Cursor,
|
||||
renderer: &Renderer,
|
||||
clipboard: &mut dyn Clipboard,
|
||||
shell: &mut Shell<'_, Message>,
|
||||
|
|
@ -564,7 +572,7 @@ mod toast {
|
|||
state,
|
||||
event.clone(),
|
||||
layout,
|
||||
cursor_position,
|
||||
cursor,
|
||||
renderer,
|
||||
clipboard,
|
||||
&mut local_shell,
|
||||
|
|
@ -584,10 +592,10 @@ mod toast {
|
|||
fn draw(
|
||||
&self,
|
||||
renderer: &mut Renderer,
|
||||
theme: &<Renderer as iced_native::Renderer>::Theme,
|
||||
theme: &<Renderer as advanced::Renderer>::Theme,
|
||||
style: &renderer::Style,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
cursor: mouse::Cursor,
|
||||
) {
|
||||
let viewport = layout.bounds();
|
||||
|
||||
|
|
@ -598,13 +606,7 @@ mod toast {
|
|||
.zip(layout.children())
|
||||
{
|
||||
child.as_widget().draw(
|
||||
state,
|
||||
renderer,
|
||||
theme,
|
||||
style,
|
||||
layout,
|
||||
cursor_position,
|
||||
&viewport,
|
||||
state, renderer, theme, style, layout, cursor, &viewport,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -613,7 +615,7 @@ mod toast {
|
|||
&mut self,
|
||||
layout: Layout<'_>,
|
||||
renderer: &Renderer,
|
||||
operation: &mut dyn iced_native::widget::Operation<Message>,
|
||||
operation: &mut dyn widget::Operation<Message>,
|
||||
) {
|
||||
operation.container(None, &mut |operation| {
|
||||
self.toasts
|
||||
|
|
@ -631,7 +633,7 @@ mod toast {
|
|||
fn mouse_interaction(
|
||||
&self,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
cursor: mouse::Cursor,
|
||||
viewport: &Rectangle,
|
||||
renderer: &Renderer,
|
||||
) -> mouse::Interaction {
|
||||
|
|
@ -641,18 +643,19 @@ mod toast {
|
|||
.zip(layout.children())
|
||||
.map(|((child, state), layout)| {
|
||||
child.as_widget().mouse_interaction(
|
||||
state,
|
||||
layout,
|
||||
cursor_position,
|
||||
viewport,
|
||||
renderer,
|
||||
state, layout, cursor, viewport, renderer,
|
||||
)
|
||||
})
|
||||
.max()
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
fn is_over(&self, layout: Layout<'_>, cursor_position: Point) -> bool {
|
||||
fn is_over(
|
||||
&self,
|
||||
layout: Layout<'_>,
|
||||
_renderer: &Renderer,
|
||||
cursor_position: Point,
|
||||
) -> bool {
|
||||
layout
|
||||
.children()
|
||||
.any(|layout| layout.bounds().contains(cursor_position))
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -1,5 +1,6 @@
|
|||
use iced::alignment::{self, Alignment};
|
||||
use iced::event::{self, Event};
|
||||
use iced::font::{self, Font};
|
||||
use iced::keyboard::{self, KeyCode, Modifiers};
|
||||
use iced::subscription;
|
||||
use iced::theme::{self, Theme};
|
||||
|
|
@ -9,7 +10,7 @@ use iced::widget::{
|
|||
};
|
||||
use iced::window;
|
||||
use iced::{Application, Element};
|
||||
use iced::{Color, Command, Font, Length, Settings, Subscription};
|
||||
use iced::{Color, Command, Length, Settings, Subscription};
|
||||
|
||||
use once_cell::sync::Lazy;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
|
@ -44,6 +45,7 @@ struct State {
|
|||
#[derive(Debug, Clone)]
|
||||
enum Message {
|
||||
Loaded(Result<SavedState, LoadError>),
|
||||
FontLoaded(Result<(), font::Error>),
|
||||
Saved(Result<(), SaveError>),
|
||||
InputChanged(String),
|
||||
CreateTask,
|
||||
|
|
@ -62,7 +64,11 @@ impl Application for Todos {
|
|||
fn new(_flags: ()) -> (Todos, Command<Message>) {
|
||||
(
|
||||
Todos::Loading,
|
||||
Command::perform(SavedState::load(), Message::Loaded),
|
||||
Command::batch(vec![
|
||||
font::load(include_bytes!("../fonts/icons.ttf").as_slice())
|
||||
.map(Message::FontLoaded),
|
||||
Command::perform(SavedState::load(), Message::Loaded),
|
||||
]),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -204,15 +210,12 @@ impl Application for Todos {
|
|||
.style(Color::from([0.5, 0.5, 0.5]))
|
||||
.horizontal_alignment(alignment::Horizontal::Center);
|
||||
|
||||
let input = text_input(
|
||||
"What needs to be done?",
|
||||
input_value,
|
||||
Message::InputChanged,
|
||||
)
|
||||
.id(INPUT_ID.clone())
|
||||
.padding(15)
|
||||
.size(30)
|
||||
.on_submit(Message::CreateTask);
|
||||
let input = text_input("What needs to be done?", input_value)
|
||||
.id(INPUT_ID.clone())
|
||||
.on_input(Message::InputChanged)
|
||||
.on_submit(Message::CreateTask)
|
||||
.padding(15)
|
||||
.size(30);
|
||||
|
||||
let controls = view_controls(tasks, *filter);
|
||||
let filtered_tasks =
|
||||
|
|
@ -361,7 +364,8 @@ impl Task {
|
|||
self.completed,
|
||||
TaskMessage::Completed,
|
||||
)
|
||||
.width(Length::Fill);
|
||||
.width(Length::Fill)
|
||||
.text_shaping(text::Shaping::Advanced);
|
||||
|
||||
row![
|
||||
checkbox,
|
||||
|
|
@ -375,21 +379,23 @@ impl Task {
|
|||
.into()
|
||||
}
|
||||
TaskState::Editing => {
|
||||
let text_input = text_input(
|
||||
"Describe your task...",
|
||||
&self.description,
|
||||
TaskMessage::DescriptionEdited,
|
||||
)
|
||||
.id(Self::text_input_id(i))
|
||||
.on_submit(TaskMessage::FinishEdition)
|
||||
.padding(10);
|
||||
let text_input =
|
||||
text_input("Describe your task...", &self.description)
|
||||
.id(Self::text_input_id(i))
|
||||
.on_input(TaskMessage::DescriptionEdited)
|
||||
.on_submit(TaskMessage::FinishEdition)
|
||||
.padding(10);
|
||||
|
||||
row![
|
||||
text_input,
|
||||
button(row![delete_icon(), "Delete"].spacing(10))
|
||||
.on_press(TaskMessage::Delete)
|
||||
.padding(10)
|
||||
.style(theme::Button::Destructive)
|
||||
button(
|
||||
row![delete_icon(), "Delete"]
|
||||
.spacing(10)
|
||||
.align_items(Alignment::Center)
|
||||
)
|
||||
.on_press(TaskMessage::Delete)
|
||||
.padding(10)
|
||||
.style(theme::Button::Destructive)
|
||||
]
|
||||
.spacing(20)
|
||||
.align_items(Alignment::Center)
|
||||
|
|
@ -403,7 +409,7 @@ fn view_controls(tasks: &[Task], current_filter: Filter) -> Element<Message> {
|
|||
let tasks_left = tasks.iter().filter(|task| !task.completed).count();
|
||||
|
||||
let filter_button = |label, filter, current_filter| {
|
||||
let label = text(label).size(16);
|
||||
let label = text(label);
|
||||
|
||||
let button = button(label).style(if filter == current_filter {
|
||||
theme::Button::Primary
|
||||
|
|
@ -420,8 +426,7 @@ fn view_controls(tasks: &[Task], current_filter: Filter) -> Element<Message> {
|
|||
tasks_left,
|
||||
if tasks_left == 1 { "task" } else { "tasks" }
|
||||
))
|
||||
.width(Length::Fill)
|
||||
.size(16),
|
||||
.width(Length::Fill),
|
||||
row![
|
||||
filter_button("All", Filter::All, current_filter),
|
||||
filter_button("Active", Filter::Active, current_filter),
|
||||
|
|
@ -435,19 +440,16 @@ fn view_controls(tasks: &[Task], current_filter: Filter) -> Element<Message> {
|
|||
.into()
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[derive(
|
||||
Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize,
|
||||
)]
|
||||
pub enum Filter {
|
||||
#[default]
|
||||
All,
|
||||
Active,
|
||||
Completed,
|
||||
}
|
||||
|
||||
impl Default for Filter {
|
||||
fn default() -> Self {
|
||||
Filter::All
|
||||
}
|
||||
}
|
||||
|
||||
impl Filter {
|
||||
fn matches(&self, task: &Task) -> bool {
|
||||
match self {
|
||||
|
|
@ -485,17 +487,13 @@ fn empty_message(message: &str) -> Element<'_, Message> {
|
|||
}
|
||||
|
||||
// Fonts
|
||||
const ICONS: Font = Font::External {
|
||||
name: "Icons",
|
||||
bytes: include_bytes!("../../todos/fonts/icons.ttf"),
|
||||
};
|
||||
const ICONS: Font = Font::with_name("Iced-Todos-Icons");
|
||||
|
||||
fn icon(unicode: char) -> Text<'static> {
|
||||
text(unicode.to_string())
|
||||
.font(ICONS)
|
||||
.width(20)
|
||||
.horizontal_alignment(alignment::Horizontal::Center)
|
||||
.size(20)
|
||||
}
|
||||
|
||||
fn edit_icon() -> Text<'static> {
|
||||
|
|
|
|||
|
|
@ -7,4 +7,4 @@ publish = false
|
|||
|
||||
[dependencies]
|
||||
iced = { path = "../..", features = ["image", "debug"] }
|
||||
env_logger = "0.8"
|
||||
env_logger = "0.10.0"
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ use iced::widget::{
|
|||
scrollable, slider, text, text_input, toggler, vertical_space,
|
||||
};
|
||||
use iced::widget::{Button, Column, Container, Slider};
|
||||
use iced::{Color, Element, Length, Renderer, Sandbox, Settings};
|
||||
use iced::{Color, Element, Font, Length, Renderer, Sandbox, Settings};
|
||||
|
||||
pub fn main() -> iced::Result {
|
||||
env_logger::init();
|
||||
|
|
@ -62,11 +62,8 @@ impl Sandbox for Tour {
|
|||
controls = controls.push(horizontal_space(Length::Fill));
|
||||
|
||||
if steps.can_continue() {
|
||||
controls = controls.push(
|
||||
button("Next")
|
||||
.on_press(Message::NextPressed)
|
||||
.style(theme::Button::Primary),
|
||||
);
|
||||
controls =
|
||||
controls.push(button("Next").on_press(Message::NextPressed));
|
||||
}
|
||||
|
||||
let content: Element<_> = column![
|
||||
|
|
@ -127,6 +124,7 @@ impl Steps {
|
|||
Step::TextInput {
|
||||
value: String::new(),
|
||||
is_secure: false,
|
||||
is_showing_icon: false,
|
||||
},
|
||||
Step::Debugger,
|
||||
Step::End,
|
||||
|
|
@ -171,14 +169,32 @@ impl Steps {
|
|||
|
||||
enum Step {
|
||||
Welcome,
|
||||
Slider { value: u8 },
|
||||
RowsAndColumns { layout: Layout, spacing: u16 },
|
||||
Text { size: u16, color: Color },
|
||||
Radio { selection: Option<Language> },
|
||||
Toggler { can_continue: bool },
|
||||
Image { width: u16 },
|
||||
Slider {
|
||||
value: u8,
|
||||
},
|
||||
RowsAndColumns {
|
||||
layout: Layout,
|
||||
spacing: u16,
|
||||
},
|
||||
Text {
|
||||
size: u16,
|
||||
color: Color,
|
||||
},
|
||||
Radio {
|
||||
selection: Option<Language>,
|
||||
},
|
||||
Toggler {
|
||||
can_continue: bool,
|
||||
},
|
||||
Image {
|
||||
width: u16,
|
||||
},
|
||||
Scrollable,
|
||||
TextInput { value: String, is_secure: bool },
|
||||
TextInput {
|
||||
value: String,
|
||||
is_secure: bool,
|
||||
is_showing_icon: bool,
|
||||
},
|
||||
Debugger,
|
||||
End,
|
||||
}
|
||||
|
|
@ -194,6 +210,7 @@ pub enum StepMessage {
|
|||
ImageWidthChanged(u16),
|
||||
InputChanged(String),
|
||||
ToggleSecureInput(bool),
|
||||
ToggleTextInputIcon(bool),
|
||||
DebugToggled(bool),
|
||||
TogglerChanged(bool),
|
||||
}
|
||||
|
|
@ -256,6 +273,14 @@ impl<'a> Step {
|
|||
*can_continue = value;
|
||||
}
|
||||
}
|
||||
StepMessage::ToggleTextInputIcon(toggle) => {
|
||||
if let Step::TextInput {
|
||||
is_showing_icon, ..
|
||||
} = self
|
||||
{
|
||||
*is_showing_icon = toggle
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -303,9 +328,11 @@ impl<'a> Step {
|
|||
Self::rows_and_columns(*layout, *spacing)
|
||||
}
|
||||
Step::Scrollable => Self::scrollable(),
|
||||
Step::TextInput { value, is_secure } => {
|
||||
Self::text_input(value, *is_secure)
|
||||
}
|
||||
Step::TextInput {
|
||||
value,
|
||||
is_secure,
|
||||
is_showing_icon,
|
||||
} => Self::text_input(value, *is_secure, *is_showing_icon),
|
||||
Step::Debugger => Self::debugger(debug),
|
||||
Step::End => Self::end(),
|
||||
}
|
||||
|
|
@ -530,14 +557,25 @@ impl<'a> Step {
|
|||
)
|
||||
}
|
||||
|
||||
fn text_input(value: &str, is_secure: bool) -> Column<'a, StepMessage> {
|
||||
let text_input = text_input(
|
||||
"Type something to continue...",
|
||||
value,
|
||||
StepMessage::InputChanged,
|
||||
)
|
||||
.padding(10)
|
||||
.size(30);
|
||||
fn text_input(
|
||||
value: &str,
|
||||
is_secure: bool,
|
||||
is_showing_icon: bool,
|
||||
) -> Column<'a, StepMessage> {
|
||||
let mut text_input = text_input("Type something to continue...", value)
|
||||
.on_input(StepMessage::InputChanged)
|
||||
.padding(10)
|
||||
.size(30);
|
||||
|
||||
if is_showing_icon {
|
||||
text_input = text_input.icon(text_input::Icon {
|
||||
font: Font::default(),
|
||||
code_point: '🚀',
|
||||
size: Some(28.0),
|
||||
spacing: 10.0,
|
||||
side: text_input::Side::Right,
|
||||
});
|
||||
}
|
||||
|
||||
Self::container("Text input")
|
||||
.push("Use a text input to ask for different kinds of information.")
|
||||
|
|
@ -551,6 +589,11 @@ impl<'a> Step {
|
|||
is_secure,
|
||||
StepMessage::ToggleSecureInput,
|
||||
))
|
||||
.push(checkbox(
|
||||
"Show icon",
|
||||
is_showing_icon,
|
||||
StepMessage::ToggleTextInputIcon,
|
||||
))
|
||||
.push(
|
||||
"A text input produces a message every time it changes. It is \
|
||||
very easy to keep track of its contents:",
|
||||
|
|
|
|||
|
|
@ -7,4 +7,3 @@ publish = false
|
|||
|
||||
[dependencies]
|
||||
iced = { path = "../.." }
|
||||
iced_native = { path = "../../native" }
|
||||
|
|
|
|||
|
|
@ -1,12 +1,10 @@
|
|||
use iced::event::{Event, MacOS, PlatformSpecific};
|
||||
use iced::executor;
|
||||
use iced::subscription;
|
||||
use iced::widget::{container, text};
|
||||
use iced::{
|
||||
Application, Command, Element, Length, Settings, Subscription, Theme,
|
||||
};
|
||||
use iced_native::{
|
||||
event::{MacOS, PlatformSpecific},
|
||||
Event,
|
||||
};
|
||||
|
||||
pub fn main() -> iced::Result {
|
||||
App::run(Settings::default())
|
||||
|
|
@ -19,7 +17,7 @@ struct App {
|
|||
|
||||
#[derive(Debug, Clone)]
|
||||
enum Message {
|
||||
EventOccurred(iced_native::Event),
|
||||
EventOccurred(Event),
|
||||
}
|
||||
|
||||
impl Application for App {
|
||||
|
|
@ -52,7 +50,7 @@ impl Application for App {
|
|||
}
|
||||
|
||||
fn subscription(&self) -> Subscription<Message> {
|
||||
iced_native::subscription::events().map(Message::EventOccurred)
|
||||
subscription::events().map(Message::EventOccurred)
|
||||
}
|
||||
|
||||
fn view(&self) -> Element<Message> {
|
||||
|
|
|
|||
|
|
@ -7,8 +7,6 @@ publish = false
|
|||
|
||||
[dependencies]
|
||||
iced = { path = "../..", features = ["tokio", "debug"] }
|
||||
iced_native = { path = "../../native" }
|
||||
iced_futures = { path = "../../futures" }
|
||||
once_cell = "1.15"
|
||||
|
||||
[dependencies.async-tungstenite]
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
pub mod server;
|
||||
|
||||
use iced_futures::futures;
|
||||
use iced_native::subscription::{self, Subscription};
|
||||
use iced::futures;
|
||||
use iced::subscription::{self, Subscription};
|
||||
|
||||
use futures::channel::mpsc;
|
||||
use futures::sink::SinkExt;
|
||||
|
|
@ -13,63 +13,67 @@ use std::fmt;
|
|||
pub fn connect() -> Subscription<Event> {
|
||||
struct Connect;
|
||||
|
||||
subscription::unfold(
|
||||
subscription::channel(
|
||||
std::any::TypeId::of::<Connect>(),
|
||||
State::Disconnected,
|
||||
|state| async move {
|
||||
match state {
|
||||
State::Disconnected => {
|
||||
const ECHO_SERVER: &str = "ws://localhost:3030";
|
||||
100,
|
||||
|mut output| async move {
|
||||
let mut state = State::Disconnected;
|
||||
|
||||
match async_tungstenite::tokio::connect_async(ECHO_SERVER)
|
||||
loop {
|
||||
match &mut state {
|
||||
State::Disconnected => {
|
||||
const ECHO_SERVER: &str = "ws://127.0.0.1:3030";
|
||||
|
||||
match async_tungstenite::tokio::connect_async(
|
||||
ECHO_SERVER,
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok((websocket, _)) => {
|
||||
let (sender, receiver) = mpsc::channel(100);
|
||||
{
|
||||
Ok((websocket, _)) => {
|
||||
let (sender, receiver) = mpsc::channel(100);
|
||||
|
||||
(
|
||||
Some(Event::Connected(Connection(sender))),
|
||||
State::Connected(websocket, receiver),
|
||||
)
|
||||
}
|
||||
Err(_) => {
|
||||
tokio::time::sleep(
|
||||
tokio::time::Duration::from_secs(1),
|
||||
)
|
||||
.await;
|
||||
let _ = output
|
||||
.send(Event::Connected(Connection(sender)))
|
||||
.await;
|
||||
|
||||
(Some(Event::Disconnected), State::Disconnected)
|
||||
}
|
||||
}
|
||||
}
|
||||
State::Connected(mut websocket, mut input) => {
|
||||
let mut fused_websocket = websocket.by_ref().fuse();
|
||||
state = State::Connected(websocket, receiver);
|
||||
}
|
||||
Err(_) => {
|
||||
tokio::time::sleep(
|
||||
tokio::time::Duration::from_secs(1),
|
||||
)
|
||||
.await;
|
||||
|
||||
futures::select! {
|
||||
received = fused_websocket.select_next_some() => {
|
||||
match received {
|
||||
Ok(tungstenite::Message::Text(message)) => {
|
||||
(
|
||||
Some(Event::MessageReceived(Message::User(message))),
|
||||
State::Connected(websocket, input)
|
||||
)
|
||||
}
|
||||
Ok(_) => {
|
||||
(None, State::Connected(websocket, input))
|
||||
}
|
||||
Err(_) => {
|
||||
(Some(Event::Disconnected), State::Disconnected)
|
||||
}
|
||||
let _ = output.send(Event::Disconnected).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
State::Connected(websocket, input) => {
|
||||
let mut fused_websocket = websocket.by_ref().fuse();
|
||||
|
||||
message = input.select_next_some() => {
|
||||
let result = websocket.send(tungstenite::Message::Text(message.to_string())).await;
|
||||
futures::select! {
|
||||
received = fused_websocket.select_next_some() => {
|
||||
match received {
|
||||
Ok(tungstenite::Message::Text(message)) => {
|
||||
let _ = output.send(Event::MessageReceived(Message::User(message))).await;
|
||||
}
|
||||
Err(_) => {
|
||||
let _ = output.send(Event::Disconnected).await;
|
||||
|
||||
if result.is_ok() {
|
||||
(None, State::Connected(websocket, input))
|
||||
} else {
|
||||
(Some(Event::Disconnected), State::Disconnected)
|
||||
state = State::Disconnected;
|
||||
}
|
||||
Ok(_) => continue,
|
||||
}
|
||||
}
|
||||
|
||||
message = input.select_next_some() => {
|
||||
let result = websocket.send(tungstenite::Message::Text(message.to_string())).await;
|
||||
|
||||
if result.is_err() {
|
||||
let _ = output.send(Event::Disconnected).await;
|
||||
|
||||
state = State::Disconnected;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use iced_futures::futures;
|
||||
use iced::futures;
|
||||
|
||||
use futures::channel::mpsc;
|
||||
use futures::{SinkExt, StreamExt};
|
||||
|
|
|
|||
|
|
@ -125,12 +125,9 @@ impl Application for WebSocket {
|
|||
};
|
||||
|
||||
let new_message_input = {
|
||||
let mut input = text_input(
|
||||
"Type a message...",
|
||||
&self.new_message,
|
||||
Message::NewMessageChanged,
|
||||
)
|
||||
.padding(10);
|
||||
let mut input = text_input("Type a message...", &self.new_message)
|
||||
.on_input(Message::NewMessageChanged)
|
||||
.padding(10);
|
||||
|
||||
let mut button = button(
|
||||
text("Send")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue