Merge branch 'master' into non-uniform-border-radius-for-quads

This commit is contained in:
Héctor Ramón Jiménez 2022-12-02 18:53:21 +01:00
commit 4029a1cdaa
No known key found for this signature in database
GPG key ID: 140CC052C94F138E
147 changed files with 4828 additions and 2184 deletions

View file

@ -0,0 +1,11 @@
[package]
name = "geometry"
version = "0.1.0"
authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]
edition = "2021"
publish = false
[dependencies]
iced = { path = "../.." }
iced_native = { path = "../../native" }
iced_graphics = { path = "../../graphics" }

View file

@ -0,0 +1,18 @@
## Geometry
A custom widget showcasing how to draw geometry with the `Mesh2D` primitive in [`iced_wgpu`](../../wgpu).
The __[`main`]__ file contains all the code of the example.
<div align="center">
<a href="https://gfycat.com/activeunfitkangaroo">
<img src="https://thumbs.gfycat.com/ActiveUnfitKangaroo-small.gif">
</a>
</div>
You can run it with `cargo run`:
```
cargo run --package geometry
```
[`main`]: src/main.rs

View file

@ -0,0 +1,212 @@
//! 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,
};
#[derive(Debug, Clone, Copy, Default)]
pub struct Rainbow;
pub fn rainbow() -> Rainbow {
Rainbow
}
impl<Message, B, T> Widget<Message, Renderer<B, T>> for Rainbow
where
B: Backend,
{
fn width(&self) -> Length {
Length::Fill
}
fn height(&self) -> Length {
Length::Shrink
}
fn layout(
&self,
_renderer: &Renderer<B, T>,
limits: &layout::Limits,
) -> layout::Node {
let size = limits.width(Length::Fill).resolve(Size::ZERO);
layout::Node::new(Size::new(size.width, size.width))
}
fn draw(
&self,
_tree: &widget::Tree,
renderer: &mut Renderer<B, T>,
_theme: &T,
_style: &renderer::Style,
layout: Layout<'_>,
cursor_position: Point,
_viewport: &Rectangle,
) {
use iced_graphics::triangle::Mesh2D;
use iced_native::Renderer as _;
let b = layout.bounds();
// R O Y G B I V
let color_r = [1.0, 0.0, 0.0, 1.0];
let color_o = [1.0, 0.5, 0.0, 1.0];
let color_y = [1.0, 1.0, 0.0, 1.0];
let color_g = [0.0, 1.0, 0.0, 1.0];
let color_gb = [0.0, 1.0, 0.5, 1.0];
let color_b = [0.0, 0.2, 1.0, 1.0];
let color_i = [0.5, 0.0, 1.0, 1.0];
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]
} else {
[b.width / 2.0, b.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 mesh = Primitive::SolidMesh {
size: b.size(),
buffers: Mesh2D {
vertices: vec![
ColoredVertex2D {
position: posn_center,
color: [1.0, 1.0, 1.0, 1.0],
},
ColoredVertex2D {
position: posn_tl,
color: color_r,
},
ColoredVertex2D {
position: posn_t,
color: color_o,
},
ColoredVertex2D {
position: posn_tr,
color: color_y,
},
ColoredVertex2D {
position: posn_r,
color: color_g,
},
ColoredVertex2D {
position: posn_br,
color: color_gb,
},
ColoredVertex2D {
position: posn_b,
color: color_b,
},
ColoredVertex2D {
position: posn_bl,
color: color_i,
},
ColoredVertex2D {
position: posn_l,
color: color_v,
},
],
indices: vec![
0, 1, 2, // TL
0, 2, 3, // T
0, 3, 4, // TR
0, 4, 5, // R
0, 5, 6, // BR
0, 6, 7, // B
0, 7, 8, // BL
0, 8, 1, // L
],
},
};
renderer.with_translation(Vector::new(b.x, b.y), |renderer| {
renderer.draw_primitive(mesh);
});
}
}
impl<'a, Message, B, T> From<Rainbow> for Element<'a, Message, Renderer<B, T>>
where
B: Backend,
{
fn from(rainbow: Rainbow) -> Self {
Self::new(rainbow)
}
}
}
use iced::widget::{column, container, scrollable};
use iced::{Element, Length, Sandbox, Settings};
use rainbow::rainbow;
pub fn main() -> iced::Result {
Example::run(Settings::default())
}
struct Example;
impl Sandbox for Example {
type Message = ();
fn new() -> Self {
Self
}
fn title(&self) -> String {
String::from("Custom 2D geometry - Iced")
}
fn update(&mut self, _: ()) {}
fn view(&self) -> Element<()> {
let content = column![
rainbow(),
"In this example we draw a custom widget Rainbow, using \
the Mesh2D primitive. This primitive supplies a list of \
triangles, expressed as vertices and indices.",
"Move your cursor over it, and see the center vertex \
follow you!",
"Every Vertex2D defines its own color. You could use the \
Mesh2D primitive to render virtually any two-dimensional \
geometry for your widget.",
]
.padding(20)
.spacing(20)
.max_width(500);
let scrollable =
scrollable(container(content).width(Length::Fill).center_x());
container(scrollable)
.width(Length::Fill)
.height(Length::Fill)
.center_y()
.into()
}
}

10
examples/modal/Cargo.toml Normal file
View file

@ -0,0 +1,10 @@
[package]
name = "modal"
version = "0.1.0"
authors = ["tarkah <admin@tarkah.dev>"]
edition = "2021"
publish = false
[dependencies]
iced = { path = "../..", features = [] }
iced_native = { path = "../../native" }

475
examples/modal/src/main.rs Normal file
View file

@ -0,0 +1,475 @@
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,
};
use self::modal::Modal;
pub fn main() -> iced::Result {
App::run(Settings::default())
}
#[derive(Default)]
struct App {
show_modal: bool,
email: String,
password: String,
}
#[derive(Debug, Clone)]
enum Message {
ShowModal,
HideModal,
Email(String),
Password(String),
Submit,
Event(Event),
}
impl Application for App {
type Executor = executor::Default;
type Message = Message;
type Theme = iced::Theme;
type Flags = ();
fn new(_flags: ()) -> (Self, Command<Message>) {
(App::default(), Command::none())
}
fn title(&self) -> String {
String::from("Modal - Iced")
}
fn subscription(&self) -> Subscription<Self::Message> {
subscription::events().map(Message::Event)
}
fn update(&mut self, message: Message) -> Command<Message> {
match message {
Message::ShowModal => {
self.show_modal = true;
widget::focus_next()
}
Message::HideModal => {
self.hide_modal();
Command::none()
}
Message::Email(email) => {
self.email = email;
Command::none()
}
Message::Password(password) => {
self.password = password;
Command::none()
}
Message::Submit => {
if !self.email.is_empty() && !self.password.is_empty() {
self.hide_modal();
}
Command::none()
}
Message::Event(event) => match event {
Event::Keyboard(keyboard::Event::KeyPressed {
key_code: keyboard::KeyCode::Tab,
modifiers,
}) => {
if modifiers.shift() {
widget::focus_previous()
} else {
widget::focus_next()
}
}
Event::Keyboard(keyboard::Event::KeyPressed {
key_code: keyboard::KeyCode::Escape,
..
}) => {
self.hide_modal();
Command::none()
}
_ => Command::none(),
},
}
}
fn view(&self) -> Element<Message> {
let content = container(
column![
row![
text("Top Left"),
horizontal_space(Length::Fill),
text("Top Right")
]
.align_items(Alignment::Start)
.height(Length::Fill),
container(
button(text("Show Modal")).on_press(Message::ShowModal)
)
.center_x()
.center_y()
.width(Length::Fill)
.height(Length::Fill),
row![
text("Bottom Left"),
horizontal_space(Length::Fill),
text("Bottom Right")
]
.align_items(Alignment::End)
.height(Length::Fill),
]
.height(Length::Fill),
)
.padding(10)
.width(Length::Fill)
.height(Length::Fill);
if self.show_modal {
let modal = container(
column![
text("Sign Up").size(24),
column![
column![
text("Email").size(12),
text_input(
"abc@123.com",
&self.email,
Message::Email
)
.on_submit(Message::Submit)
.padding(5),
]
.spacing(5),
column![
text("Password").size(12),
text_input("", &self.password, Message::Password)
.on_submit(Message::Submit)
.password()
.padding(5),
]
.spacing(5),
button(text("Submit")).on_press(Message::HideModal),
]
.spacing(10)
]
.spacing(20),
)
.width(Length::Units(300))
.padding(10)
.style(theme::Container::Box);
Modal::new(content, modal)
.on_blur(Message::HideModal)
.into()
} else {
content.into()
}
}
}
impl App {
fn hide_modal(&mut self) {
self.show_modal = false;
self.email.clear();
self.password.clear();
}
}
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,
};
/// A widget that centers a modal element over some base element
pub struct Modal<'a, Message, Renderer> {
base: Element<'a, Message, Renderer>,
modal: Element<'a, Message, Renderer>,
on_blur: Option<Message>,
}
impl<'a, Message, Renderer> Modal<'a, Message, Renderer> {
/// Returns a new [`Modal`]
pub fn new(
base: impl Into<Element<'a, Message, Renderer>>,
modal: impl Into<Element<'a, Message, Renderer>>,
) -> Self {
Self {
base: base.into(),
modal: modal.into(),
on_blur: None,
}
}
/// Sets the message that will be produces when the background
/// of the [`Modal`] is pressed
pub fn on_blur(self, on_blur: Message) -> Self {
Self {
on_blur: Some(on_blur),
..self
}
}
}
impl<'a, Message, Renderer> Widget<Message, Renderer>
for Modal<'a, Message, Renderer>
where
Renderer: iced_native::Renderer,
Message: Clone,
{
fn children(&self) -> Vec<Tree> {
vec![Tree::new(&self.base), Tree::new(&self.modal)]
}
fn diff(&self, tree: &mut Tree) {
tree.diff_children(&[&self.base, &self.modal]);
}
fn width(&self) -> Length {
self.base.as_widget().width()
}
fn height(&self) -> Length {
self.base.as_widget().height()
}
fn layout(
&self,
renderer: &Renderer,
limits: &layout::Limits,
) -> layout::Node {
self.base.as_widget().layout(renderer, limits)
}
fn on_event(
&mut self,
state: &mut Tree,
event: Event,
layout: Layout<'_>,
cursor_position: Point,
renderer: &Renderer,
clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
) -> event::Status {
self.base.as_widget_mut().on_event(
&mut state.children[0],
event,
layout,
cursor_position,
renderer,
clipboard,
shell,
)
}
fn draw(
&self,
state: &Tree,
renderer: &mut Renderer,
theme: &<Renderer as iced_native::Renderer>::Theme,
style: &renderer::Style,
layout: Layout<'_>,
cursor_position: Point,
viewport: &Rectangle,
) {
self.base.as_widget().draw(
&state.children[0],
renderer,
theme,
style,
layout,
cursor_position,
viewport,
);
}
fn overlay<'b>(
&'b mut self,
state: &'b mut Tree,
layout: Layout<'_>,
_renderer: &Renderer,
) -> Option<overlay::Element<'b, Message, Renderer>> {
Some(overlay::Element::new(
layout.position(),
Box::new(Overlay {
content: &mut self.modal,
tree: &mut state.children[1],
size: layout.bounds().size(),
on_blur: self.on_blur.clone(),
}),
))
}
fn mouse_interaction(
&self,
state: &Tree,
layout: Layout<'_>,
cursor_position: Point,
viewport: &Rectangle,
renderer: &Renderer,
) -> mouse::Interaction {
self.base.as_widget().mouse_interaction(
&state.children[0],
layout,
cursor_position,
viewport,
renderer,
)
}
fn operate(
&self,
state: &mut Tree,
layout: Layout<'_>,
operation: &mut dyn widget::Operation<Message>,
) {
self.base.as_widget().operate(
&mut state.children[0],
layout,
operation,
);
}
}
struct Overlay<'a, 'b, Message, Renderer> {
content: &'b mut Element<'a, Message, Renderer>,
tree: &'b mut Tree,
size: Size,
on_blur: Option<Message>,
}
impl<'a, 'b, Message, Renderer> overlay::Overlay<Message, Renderer>
for Overlay<'a, 'b, Message, Renderer>
where
Renderer: iced_native::Renderer,
Message: Clone,
{
fn layout(
&self,
renderer: &Renderer,
_bounds: Size,
position: Point,
) -> layout::Node {
let limits = layout::Limits::new(Size::ZERO, self.size)
.width(Length::Fill)
.height(Length::Fill);
let mut child = self.content.as_widget().layout(renderer, &limits);
child.align(Alignment::Center, Alignment::Center, limits.max());
let mut node = layout::Node::with_children(self.size, vec![child]);
node.move_to(position);
node
}
fn on_event(
&mut self,
event: Event,
layout: Layout<'_>,
cursor_position: Point,
renderer: &Renderer,
clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
) -> event::Status {
let content_bounds = layout.children().next().unwrap().bounds();
if let Some(message) = self.on_blur.as_ref() {
if let Event::Mouse(mouse::Event::ButtonPressed(
mouse::Button::Left,
)) = &event
{
if !content_bounds.contains(cursor_position) {
shell.publish(message.clone());
return event::Status::Captured;
}
}
}
self.content.as_widget_mut().on_event(
self.tree,
event,
layout.children().next().unwrap(),
cursor_position,
renderer,
clipboard,
shell,
)
}
fn draw(
&self,
renderer: &mut Renderer,
theme: &Renderer::Theme,
style: &renderer::Style,
layout: Layout<'_>,
cursor_position: Point,
) {
renderer.fill_quad(
renderer::Quad {
bounds: layout.bounds(),
border_radius: 0.0,
border_width: 0.0,
border_color: Color::TRANSPARENT,
},
Color {
a: 0.80,
..Color::BLACK
},
);
self.content.as_widget().draw(
self.tree,
renderer,
theme,
style,
layout.children().next().unwrap(),
cursor_position,
&layout.bounds(),
);
}
fn operate(
&mut self,
layout: Layout<'_>,
operation: &mut dyn widget::Operation<Message>,
) {
self.content.as_widget().operate(
self.tree,
layout.children().next().unwrap(),
operation,
);
}
fn mouse_interaction(
&self,
layout: Layout<'_>,
cursor_position: Point,
viewport: &Rectangle,
renderer: &Renderer,
) -> mouse::Interaction {
self.content.as_widget().mouse_interaction(
self.tree,
layout.children().next().unwrap(),
cursor_position,
viewport,
renderer,
)
}
}
impl<'a, Message, Renderer> From<Modal<'a, Message, Renderer>>
for Element<'a, Message, Renderer>
where
Renderer: 'a + iced_native::Renderer,
Message: 'a + Clone,
{
fn from(modal: Modal<'a, Message, Renderer>) -> Self {
Element::new(modal)
}
}
}

View file

@ -8,3 +8,4 @@ publish = false
[dependencies]
iced = { path = "../..", features = ["canvas", "tokio", "debug"] }
rand = "0.8.5"
env_logger = "0.9"

View file

@ -9,6 +9,8 @@ use iced::{
use rand::{thread_rng, Rng};
fn main() -> iced::Result {
env_logger::builder().format_timestamp(None).init();
ModernArt::run(Settings {
antialiasing: true,
..Settings::default()

View file

@ -29,6 +29,8 @@ enum Message {
Dragged(pane_grid::DragEvent),
Resized(pane_grid::ResizeEvent),
TogglePin(pane_grid::Pane),
Maximize(pane_grid::Pane),
Restore,
Close(pane_grid::Pane),
CloseFocused,
}
@ -114,6 +116,10 @@ impl Application for Example {
*is_pinned = !*is_pinned;
}
}
Message::Maximize(pane) => self.panes.maximize(&pane),
Message::Restore => {
self.panes.restore();
}
Message::Close(pane) => {
if let Some((_, sibling)) = self.panes.close(&pane) {
self.focus = Some(sibling);
@ -157,7 +163,7 @@ impl Application for Example {
let focus = self.focus;
let total_panes = self.panes.len();
let pane_grid = PaneGrid::new(&self.panes, |id, pane| {
let pane_grid = PaneGrid::new(&self.panes, |id, pane, is_maximized| {
let is_focused = focus == Some(id);
let pin_button = button(
@ -178,7 +184,12 @@ impl Application for Example {
.spacing(5);
let title_bar = pane_grid::TitleBar::new(title)
.controls(view_controls(id, total_panes, pane.is_pinned))
.controls(view_controls(
id,
total_panes,
pane.is_pinned,
is_maximized,
))
.padding(10)
.style(if is_focused {
style::title_bar_focused
@ -314,16 +325,35 @@ fn view_controls<'a>(
pane: pane_grid::Pane,
total_panes: usize,
is_pinned: bool,
is_maximized: bool,
) -> Element<'a, Message> {
let mut button = button(text("Close").size(14))
let mut row = row![].spacing(5);
if total_panes > 1 {
let toggle = {
let (content, message) = if is_maximized {
("Restore", Message::Restore)
} else {
("Maximize", Message::Maximize(pane))
};
button(text(content).size(14))
.style(theme::Button::Secondary)
.padding(3)
.on_press(message)
};
row = row.push(toggle);
}
let mut close = button(text("Close").size(14))
.style(theme::Button::Destructive)
.padding(3);
if total_panes > 1 && !is_pinned {
button = button.on_press(Message::Close(pane));
close = close.on_press(Message::Close(pane));
}
button.into()
row.push(close).into()
}
mod style {

View file

@ -79,10 +79,14 @@ impl Application for SolarSystem {
}
fn style(&self) -> theme::Application {
theme::Application::Custom(|_theme| application::Appearance {
background_color: Color::BLACK,
text_color: Color::WHITE,
})
fn dark_background(_theme: &Theme) -> application::Appearance {
application::Appearance {
background_color: Color::BLACK,
text_color: Color::WHITE,
}
}
theme::Application::from(dark_background as fn(&Theme) -> _)
}
fn subscription(&self) -> Subscription<Message> {

View file

@ -131,7 +131,11 @@ impl Application for Todos {
task.update(task_message);
if should_focus {
text_input::focus(Task::text_input_id(i))
let id = Task::text_input_id(i);
Command::batch(vec![
text_input::focus(id.clone()),
text_input::select_all(id),
])
} else {
Command::none()
}