Move Target to its own module
This commit is contained in:
parent
58e04af824
commit
ef056d8489
13 changed files with 96 additions and 78 deletions
86
wgpu/src/renderer/widget/button.rs
Normal file
86
wgpu/src/renderer/widget/button.rs
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
use crate::{Primitive, Renderer};
|
||||
use iced_native::{
|
||||
button, Align, Background, Button, Color, Layout, Length, MouseCursor,
|
||||
Node, Point, Rectangle, Style,
|
||||
};
|
||||
|
||||
impl button::Renderer for Renderer {
|
||||
fn node<Message>(&self, button: &Button<Message, Self>) -> Node {
|
||||
let style = Style::default()
|
||||
.width(button.width)
|
||||
.padding(button.padding)
|
||||
.min_width(Length::Units(100))
|
||||
.align_self(button.align_self)
|
||||
.align_items(Align::Stretch);
|
||||
|
||||
Node::with_children(style, vec![button.content.node(self)])
|
||||
}
|
||||
|
||||
fn draw<Message>(
|
||||
&mut self,
|
||||
button: &Button<Message, Self>,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
) -> Self::Output {
|
||||
let bounds = layout.bounds();
|
||||
|
||||
let (content, _) = button.content.draw(
|
||||
self,
|
||||
layout.children().next().unwrap(),
|
||||
cursor_position,
|
||||
);
|
||||
|
||||
let is_mouse_over = bounds.contains(cursor_position);
|
||||
|
||||
// TODO: Render proper shadows
|
||||
// TODO: Make hovering and pressed styles configurable
|
||||
let shadow_offset = if is_mouse_over {
|
||||
if button.state.is_pressed {
|
||||
0.0
|
||||
} else {
|
||||
2.0
|
||||
}
|
||||
} else {
|
||||
1.0
|
||||
};
|
||||
|
||||
(
|
||||
Primitive::Group {
|
||||
primitives: vec![
|
||||
Primitive::Quad {
|
||||
bounds: Rectangle {
|
||||
x: bounds.x + 1.0,
|
||||
y: bounds.y + shadow_offset,
|
||||
..bounds
|
||||
},
|
||||
background: Background::Color(Color {
|
||||
r: 0.0,
|
||||
b: 0.0,
|
||||
g: 0.0,
|
||||
a: 0.5,
|
||||
}),
|
||||
border_radius: button.border_radius,
|
||||
},
|
||||
Primitive::Quad {
|
||||
bounds,
|
||||
background: button.background.unwrap_or(
|
||||
Background::Color(Color {
|
||||
r: 0.8,
|
||||
b: 0.8,
|
||||
g: 0.8,
|
||||
a: 1.0,
|
||||
}),
|
||||
),
|
||||
border_radius: button.border_radius,
|
||||
},
|
||||
content,
|
||||
],
|
||||
},
|
||||
if is_mouse_over {
|
||||
MouseCursor::Pointer
|
||||
} else {
|
||||
MouseCursor::OutOfBounds
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
107
wgpu/src/renderer/widget/checkbox.rs
Normal file
107
wgpu/src/renderer/widget/checkbox.rs
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
use crate::{Primitive, Renderer};
|
||||
use iced_native::{
|
||||
checkbox, text, text::HorizontalAlignment, text::VerticalAlignment, Align,
|
||||
Background, Checkbox, Color, Column, Layout, Length, MouseCursor, Node,
|
||||
Point, Rectangle, Row, Text, Widget,
|
||||
};
|
||||
|
||||
const SIZE: f32 = 28.0;
|
||||
|
||||
impl checkbox::Renderer for Renderer {
|
||||
fn node<Message>(&self, checkbox: &Checkbox<Message>) -> Node {
|
||||
Row::<(), Self>::new()
|
||||
.width(Length::Fill)
|
||||
.spacing(15)
|
||||
.align_items(Align::Center)
|
||||
.push(
|
||||
Column::new()
|
||||
.width(Length::Units(SIZE as u16))
|
||||
.height(Length::Units(SIZE as u16)),
|
||||
)
|
||||
.push(Text::new(&checkbox.label))
|
||||
.node(self)
|
||||
}
|
||||
|
||||
fn draw<Message>(
|
||||
&mut self,
|
||||
checkbox: &Checkbox<Message>,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
) -> Self::Output {
|
||||
let bounds = layout.bounds();
|
||||
let mut children = layout.children();
|
||||
|
||||
let checkbox_layout = children.next().unwrap();
|
||||
let label_layout = children.next().unwrap();
|
||||
let checkbox_bounds = checkbox_layout.bounds();
|
||||
|
||||
let (label, _) = text::Renderer::draw(
|
||||
self,
|
||||
&Text::new(&checkbox.label),
|
||||
label_layout,
|
||||
);
|
||||
|
||||
let is_mouse_over = bounds.contains(cursor_position);
|
||||
|
||||
let (checkbox_border, checkbox_box) = (
|
||||
Primitive::Quad {
|
||||
bounds: checkbox_bounds,
|
||||
background: Background::Color(Color {
|
||||
r: 0.6,
|
||||
g: 0.6,
|
||||
b: 0.6,
|
||||
a: 1.0,
|
||||
}),
|
||||
border_radius: 6,
|
||||
},
|
||||
Primitive::Quad {
|
||||
bounds: Rectangle {
|
||||
x: checkbox_bounds.x + 1.0,
|
||||
y: checkbox_bounds.y + 1.0,
|
||||
width: checkbox_bounds.width - 2.0,
|
||||
height: checkbox_bounds.height - 2.0,
|
||||
},
|
||||
background: Background::Color(if is_mouse_over {
|
||||
Color {
|
||||
r: 0.90,
|
||||
g: 0.90,
|
||||
b: 0.90,
|
||||
a: 1.0,
|
||||
}
|
||||
} else {
|
||||
Color {
|
||||
r: 0.95,
|
||||
g: 0.95,
|
||||
b: 0.95,
|
||||
a: 1.0,
|
||||
}
|
||||
}),
|
||||
border_radius: 6,
|
||||
},
|
||||
);
|
||||
|
||||
(
|
||||
Primitive::Group {
|
||||
primitives: if checkbox.is_checked {
|
||||
// TODO: Draw an actual icon
|
||||
let (check, _) = text::Renderer::draw(
|
||||
self,
|
||||
&Text::new("X")
|
||||
.horizontal_alignment(HorizontalAlignment::Center)
|
||||
.vertical_alignment(VerticalAlignment::Center),
|
||||
checkbox_layout,
|
||||
);
|
||||
|
||||
vec![checkbox_border, checkbox_box, check, label]
|
||||
} else {
|
||||
vec![checkbox_border, checkbox_box, label]
|
||||
},
|
||||
},
|
||||
if is_mouse_over {
|
||||
MouseCursor::Pointer
|
||||
} else {
|
||||
MouseCursor::OutOfBounds
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
34
wgpu/src/renderer/widget/column.rs
Normal file
34
wgpu/src/renderer/widget/column.rs
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
use crate::{Primitive, Renderer};
|
||||
use iced_native::{column, Column, Layout, MouseCursor, Point};
|
||||
|
||||
impl column::Renderer for Renderer {
|
||||
fn draw<Message>(
|
||||
&mut self,
|
||||
column: &Column<'_, Message, Self>,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
) -> Self::Output {
|
||||
let mut mouse_cursor = MouseCursor::OutOfBounds;
|
||||
|
||||
(
|
||||
Primitive::Group {
|
||||
primitives: column
|
||||
.children
|
||||
.iter()
|
||||
.zip(layout.children())
|
||||
.map(|(child, layout)| {
|
||||
let (primitive, new_mouse_cursor) =
|
||||
child.draw(self, layout, cursor_position);
|
||||
|
||||
if new_mouse_cursor > mouse_cursor {
|
||||
mouse_cursor = new_mouse_cursor;
|
||||
}
|
||||
|
||||
primitive
|
||||
})
|
||||
.collect(),
|
||||
},
|
||||
mouse_cursor,
|
||||
)
|
||||
}
|
||||
}
|
||||
34
wgpu/src/renderer/widget/image.rs
Normal file
34
wgpu/src/renderer/widget/image.rs
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
use crate::{Primitive, Renderer};
|
||||
use iced_native::{image, Image, Layout, Length, MouseCursor, Node, Style};
|
||||
|
||||
impl image::Renderer for Renderer {
|
||||
fn node(&self, image: &Image) -> Node {
|
||||
let (width, height) = self.image_pipeline.dimensions(&image.path);
|
||||
|
||||
let aspect_ratio = width as f32 / height as f32;
|
||||
|
||||
let mut style = Style::default().align_self(image.align_self);
|
||||
|
||||
// TODO: Deal with additional cases
|
||||
style = match (image.width, image.height) {
|
||||
(Length::Units(width), _) => style.width(image.width).height(
|
||||
Length::Units((width as f32 / aspect_ratio).round() as u16),
|
||||
),
|
||||
(_, _) => style
|
||||
.width(Length::Units(width as u16))
|
||||
.height(Length::Units(height as u16)),
|
||||
};
|
||||
|
||||
Node::new(style)
|
||||
}
|
||||
|
||||
fn draw(&mut self, image: &Image, layout: Layout<'_>) -> Self::Output {
|
||||
(
|
||||
Primitive::Image {
|
||||
path: image.path.clone(),
|
||||
bounds: layout.bounds(),
|
||||
},
|
||||
MouseCursor::OutOfBounds,
|
||||
)
|
||||
}
|
||||
}
|
||||
109
wgpu/src/renderer/widget/radio.rs
Normal file
109
wgpu/src/renderer/widget/radio.rs
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
use crate::{Primitive, Renderer};
|
||||
use iced_native::{
|
||||
radio, text, Align, Background, Color, Column, Layout, Length, MouseCursor,
|
||||
Node, Point, Radio, Rectangle, Row, Text, Widget,
|
||||
};
|
||||
|
||||
const SIZE: f32 = 28.0;
|
||||
const DOT_SIZE: f32 = SIZE / 2.0;
|
||||
|
||||
impl radio::Renderer for Renderer {
|
||||
fn node<Message>(&self, radio: &Radio<Message>) -> Node {
|
||||
Row::<(), Self>::new()
|
||||
.spacing(15)
|
||||
.align_items(Align::Center)
|
||||
.push(
|
||||
Column::new()
|
||||
.width(Length::Units(SIZE as u16))
|
||||
.height(Length::Units(SIZE as u16)),
|
||||
)
|
||||
.push(Text::new(&radio.label))
|
||||
.node(self)
|
||||
}
|
||||
|
||||
fn draw<Message>(
|
||||
&mut self,
|
||||
radio: &Radio<Message>,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
) -> Self::Output {
|
||||
let bounds = layout.bounds();
|
||||
let mut children = layout.children();
|
||||
|
||||
let radio_bounds = children.next().unwrap().bounds();
|
||||
let label_layout = children.next().unwrap();
|
||||
|
||||
let (label, _) =
|
||||
text::Renderer::draw(self, &Text::new(&radio.label), label_layout);
|
||||
|
||||
let is_mouse_over = bounds.contains(cursor_position);
|
||||
|
||||
let (radio_border, radio_box) = (
|
||||
Primitive::Quad {
|
||||
bounds: radio_bounds,
|
||||
background: Background::Color(Color {
|
||||
r: 0.6,
|
||||
g: 0.6,
|
||||
b: 0.6,
|
||||
a: 1.0,
|
||||
}),
|
||||
border_radius: (SIZE / 2.0) as u16,
|
||||
},
|
||||
Primitive::Quad {
|
||||
bounds: Rectangle {
|
||||
x: radio_bounds.x + 1.0,
|
||||
y: radio_bounds.y + 1.0,
|
||||
width: radio_bounds.width - 2.0,
|
||||
height: radio_bounds.height - 2.0,
|
||||
},
|
||||
background: Background::Color(if is_mouse_over {
|
||||
Color {
|
||||
r: 0.90,
|
||||
g: 0.90,
|
||||
b: 0.90,
|
||||
a: 1.0,
|
||||
}
|
||||
} else {
|
||||
Color {
|
||||
r: 0.95,
|
||||
g: 0.95,
|
||||
b: 0.95,
|
||||
a: 1.0,
|
||||
}
|
||||
}),
|
||||
border_radius: (SIZE / 2.0 - 1.0) as u16,
|
||||
},
|
||||
);
|
||||
|
||||
(
|
||||
Primitive::Group {
|
||||
primitives: if radio.is_selected {
|
||||
let radio_circle = Primitive::Quad {
|
||||
bounds: Rectangle {
|
||||
x: radio_bounds.x + DOT_SIZE / 2.0,
|
||||
y: radio_bounds.y + DOT_SIZE / 2.0,
|
||||
width: radio_bounds.width - DOT_SIZE,
|
||||
height: radio_bounds.height - DOT_SIZE,
|
||||
},
|
||||
background: Background::Color(Color {
|
||||
r: 0.30,
|
||||
g: 0.30,
|
||||
b: 0.30,
|
||||
a: 1.0,
|
||||
}),
|
||||
border_radius: (DOT_SIZE / 2.0) as u16,
|
||||
};
|
||||
|
||||
vec![radio_border, radio_box, radio_circle, label]
|
||||
} else {
|
||||
vec![radio_border, radio_box, label]
|
||||
},
|
||||
},
|
||||
if is_mouse_over {
|
||||
MouseCursor::Pointer
|
||||
} else {
|
||||
MouseCursor::OutOfBounds
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
34
wgpu/src/renderer/widget/row.rs
Normal file
34
wgpu/src/renderer/widget/row.rs
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
use crate::{Primitive, Renderer};
|
||||
use iced_native::{row, Layout, MouseCursor, Point, Row};
|
||||
|
||||
impl row::Renderer for Renderer {
|
||||
fn draw<Message>(
|
||||
&mut self,
|
||||
row: &Row<'_, Message, Self>,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
) -> Self::Output {
|
||||
let mut mouse_cursor = MouseCursor::OutOfBounds;
|
||||
|
||||
(
|
||||
Primitive::Group {
|
||||
primitives: row
|
||||
.children
|
||||
.iter()
|
||||
.zip(layout.children())
|
||||
.map(|(child, layout)| {
|
||||
let (primitive, new_mouse_cursor) =
|
||||
child.draw(self, layout, cursor_position);
|
||||
|
||||
if new_mouse_cursor > mouse_cursor {
|
||||
mouse_cursor = new_mouse_cursor;
|
||||
}
|
||||
|
||||
primitive
|
||||
})
|
||||
.collect(),
|
||||
},
|
||||
mouse_cursor,
|
||||
)
|
||||
}
|
||||
}
|
||||
129
wgpu/src/renderer/widget/scrollable.rs
Normal file
129
wgpu/src/renderer/widget/scrollable.rs
Normal file
|
|
@ -0,0 +1,129 @@
|
|||
use crate::{Primitive, Renderer};
|
||||
use iced_native::{
|
||||
scrollable, Background, Color, Layout, MouseCursor, Point, Rectangle,
|
||||
Scrollable, Widget,
|
||||
};
|
||||
|
||||
const SCROLLBAR_WIDTH: u16 = 10;
|
||||
const SCROLLBAR_MARGIN: u16 = 2;
|
||||
|
||||
fn scrollbar_bounds(bounds: Rectangle) -> Rectangle {
|
||||
Rectangle {
|
||||
x: bounds.x + bounds.width
|
||||
- f32::from(SCROLLBAR_WIDTH + 2 * SCROLLBAR_MARGIN),
|
||||
y: bounds.y,
|
||||
width: f32::from(SCROLLBAR_WIDTH + 2 * SCROLLBAR_MARGIN),
|
||||
height: bounds.height,
|
||||
}
|
||||
}
|
||||
|
||||
impl scrollable::Renderer for Renderer {
|
||||
fn is_mouse_over_scrollbar(
|
||||
&self,
|
||||
bounds: Rectangle,
|
||||
content_bounds: Rectangle,
|
||||
cursor_position: Point,
|
||||
) -> bool {
|
||||
content_bounds.height > bounds.height
|
||||
&& scrollbar_bounds(bounds).contains(cursor_position)
|
||||
}
|
||||
|
||||
fn draw<Message>(
|
||||
&mut self,
|
||||
scrollable: &Scrollable<'_, Message, Self>,
|
||||
bounds: Rectangle,
|
||||
content: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
) -> Self::Output {
|
||||
let is_mouse_over = bounds.contains(cursor_position);
|
||||
let content_bounds = content.bounds();
|
||||
|
||||
let offset = scrollable.state.offset(bounds, content_bounds);
|
||||
let is_content_overflowing = content_bounds.height > bounds.height;
|
||||
let scrollbar_bounds = scrollbar_bounds(bounds);
|
||||
let is_mouse_over_scrollbar = self.is_mouse_over_scrollbar(
|
||||
bounds,
|
||||
content_bounds,
|
||||
cursor_position,
|
||||
);
|
||||
|
||||
let cursor_position = if is_mouse_over && !is_mouse_over_scrollbar {
|
||||
Point::new(cursor_position.x, cursor_position.y + offset as f32)
|
||||
} else {
|
||||
Point::new(cursor_position.x, -1.0)
|
||||
};
|
||||
|
||||
let (content, mouse_cursor) =
|
||||
scrollable.content.draw(self, content, cursor_position);
|
||||
|
||||
let clip = Primitive::Clip {
|
||||
bounds,
|
||||
offset,
|
||||
content: Box::new(content),
|
||||
};
|
||||
|
||||
(
|
||||
if is_content_overflowing
|
||||
&& (is_mouse_over || scrollable.state.is_scrollbar_grabbed())
|
||||
{
|
||||
let ratio = bounds.height / content_bounds.height;
|
||||
let scrollbar_height = bounds.height * ratio;
|
||||
let y_offset = offset as f32 * ratio;
|
||||
|
||||
let scrollbar = Primitive::Quad {
|
||||
bounds: Rectangle {
|
||||
x: scrollbar_bounds.x + f32::from(SCROLLBAR_MARGIN),
|
||||
y: scrollbar_bounds.y + y_offset,
|
||||
width: scrollbar_bounds.width
|
||||
- f32::from(2 * SCROLLBAR_MARGIN),
|
||||
height: scrollbar_height,
|
||||
},
|
||||
background: Background::Color(Color {
|
||||
r: 0.0,
|
||||
g: 0.0,
|
||||
b: 0.0,
|
||||
a: 0.7,
|
||||
}),
|
||||
border_radius: 5,
|
||||
};
|
||||
|
||||
if is_mouse_over_scrollbar
|
||||
|| scrollable.state.is_scrollbar_grabbed()
|
||||
{
|
||||
let scrollbar_background = Primitive::Quad {
|
||||
bounds: Rectangle {
|
||||
x: scrollbar_bounds.x + f32::from(SCROLLBAR_MARGIN),
|
||||
width: scrollbar_bounds.width
|
||||
- f32::from(2 * SCROLLBAR_MARGIN),
|
||||
..scrollbar_bounds
|
||||
},
|
||||
background: Background::Color(Color {
|
||||
r: 0.0,
|
||||
g: 0.0,
|
||||
b: 0.0,
|
||||
a: 0.3,
|
||||
}),
|
||||
border_radius: 5,
|
||||
};
|
||||
|
||||
Primitive::Group {
|
||||
primitives: vec![clip, scrollbar_background, scrollbar],
|
||||
}
|
||||
} else {
|
||||
Primitive::Group {
|
||||
primitives: vec![clip, scrollbar],
|
||||
}
|
||||
}
|
||||
} else {
|
||||
clip
|
||||
},
|
||||
if is_mouse_over_scrollbar
|
||||
|| scrollable.state.is_scrollbar_grabbed()
|
||||
{
|
||||
MouseCursor::Idle
|
||||
} else {
|
||||
mouse_cursor
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
128
wgpu/src/renderer/widget/slider.rs
Normal file
128
wgpu/src/renderer/widget/slider.rs
Normal file
|
|
@ -0,0 +1,128 @@
|
|||
use crate::{Primitive, Renderer};
|
||||
use iced_native::{
|
||||
slider, Background, Color, Layout, Length, MouseCursor, Node, Point,
|
||||
Rectangle, Slider, Style,
|
||||
};
|
||||
|
||||
const HANDLE_WIDTH: f32 = 8.0;
|
||||
const HANDLE_HEIGHT: f32 = 22.0;
|
||||
|
||||
impl slider::Renderer for Renderer {
|
||||
fn node<Message>(&self, slider: &Slider<Message>) -> Node {
|
||||
let style = Style::default()
|
||||
.width(slider.width)
|
||||
.height(Length::Units(HANDLE_HEIGHT as u16))
|
||||
.min_width(Length::Units(100));
|
||||
|
||||
Node::new(style)
|
||||
}
|
||||
|
||||
fn draw<Message>(
|
||||
&mut self,
|
||||
slider: &Slider<Message>,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
) -> Self::Output {
|
||||
let bounds = layout.bounds();
|
||||
|
||||
let is_mouse_over = bounds.contains(cursor_position);
|
||||
|
||||
let rail_y = bounds.y + (bounds.height / 2.0).round();
|
||||
|
||||
let (rail_top, rail_bottom) = (
|
||||
Primitive::Quad {
|
||||
bounds: Rectangle {
|
||||
x: bounds.x,
|
||||
y: rail_y,
|
||||
width: bounds.width,
|
||||
height: 2.0,
|
||||
},
|
||||
background: Background::Color(Color {
|
||||
r: 0.6,
|
||||
g: 0.6,
|
||||
b: 0.6,
|
||||
a: 1.0,
|
||||
}),
|
||||
border_radius: 0,
|
||||
},
|
||||
Primitive::Quad {
|
||||
bounds: Rectangle {
|
||||
x: bounds.x,
|
||||
y: rail_y + 2.0,
|
||||
width: bounds.width,
|
||||
height: 2.0,
|
||||
},
|
||||
background: Background::Color(Color::WHITE),
|
||||
border_radius: 0,
|
||||
},
|
||||
);
|
||||
|
||||
let (range_start, range_end) = slider.range.clone().into_inner();
|
||||
|
||||
let handle_offset = (bounds.width - HANDLE_WIDTH)
|
||||
* ((slider.value - range_start)
|
||||
/ (range_end - range_start).max(1.0));
|
||||
|
||||
let (handle_border, handle) = (
|
||||
Primitive::Quad {
|
||||
bounds: Rectangle {
|
||||
x: bounds.x + handle_offset.round() - 1.0,
|
||||
y: rail_y - HANDLE_HEIGHT / 2.0 - 1.0,
|
||||
width: HANDLE_WIDTH + 2.0,
|
||||
height: HANDLE_HEIGHT + 2.0,
|
||||
},
|
||||
background: Background::Color(Color {
|
||||
r: 0.6,
|
||||
g: 0.6,
|
||||
b: 0.6,
|
||||
a: 1.0,
|
||||
}),
|
||||
border_radius: 5,
|
||||
},
|
||||
Primitive::Quad {
|
||||
bounds: Rectangle {
|
||||
x: bounds.x + handle_offset.round(),
|
||||
y: rail_y - HANDLE_HEIGHT / 2.0,
|
||||
width: HANDLE_WIDTH,
|
||||
height: HANDLE_HEIGHT,
|
||||
},
|
||||
background: Background::Color(if slider.state.is_dragging() {
|
||||
Color {
|
||||
r: 0.85,
|
||||
g: 0.85,
|
||||
b: 0.85,
|
||||
a: 1.0,
|
||||
}
|
||||
} else if is_mouse_over {
|
||||
Color {
|
||||
r: 0.9,
|
||||
g: 0.9,
|
||||
b: 0.9,
|
||||
a: 1.0,
|
||||
}
|
||||
} else {
|
||||
Color {
|
||||
r: 0.95,
|
||||
g: 0.95,
|
||||
b: 0.95,
|
||||
a: 1.0,
|
||||
}
|
||||
}),
|
||||
border_radius: 4,
|
||||
},
|
||||
);
|
||||
|
||||
(
|
||||
Primitive::Group {
|
||||
primitives: vec![rail_top, rail_bottom, handle_border, handle],
|
||||
},
|
||||
if slider.state.is_dragging() {
|
||||
MouseCursor::Grabbing
|
||||
} else if is_mouse_over {
|
||||
MouseCursor::Grab
|
||||
} else {
|
||||
MouseCursor::OutOfBounds
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
83
wgpu/src/renderer/widget/text.rs
Normal file
83
wgpu/src/renderer/widget/text.rs
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
use crate::{Primitive, Renderer};
|
||||
use iced_native::{text, Color, Layout, MouseCursor, Node, Style, Text};
|
||||
|
||||
use wgpu_glyph::{GlyphCruncher, Section};
|
||||
|
||||
use std::cell::RefCell;
|
||||
use std::f32;
|
||||
|
||||
impl text::Renderer for Renderer {
|
||||
fn node(&self, text: &Text) -> Node {
|
||||
let glyph_brush = self.glyph_brush.clone();
|
||||
let content = text.content.clone();
|
||||
|
||||
// TODO: Investigate why stretch tries to measure this MANY times
|
||||
// with every ancestor's bounds.
|
||||
// Bug? Using the library wrong? I should probably open an issue on
|
||||
// the stretch repository.
|
||||
// I noticed that the first measure is the one that matters in
|
||||
// practice. Here, we use a RefCell to store the cached measurement.
|
||||
let measure = RefCell::new(None);
|
||||
let size = text.size.map(f32::from).unwrap_or(20.0);
|
||||
|
||||
let style = Style::default().width(text.width);
|
||||
|
||||
iced_native::Node::with_measure(style, move |bounds| {
|
||||
let mut measure = measure.borrow_mut();
|
||||
|
||||
if measure.is_none() {
|
||||
let bounds = (
|
||||
match bounds.width {
|
||||
iced_native::Number::Undefined => f32::INFINITY,
|
||||
iced_native::Number::Defined(w) => w,
|
||||
},
|
||||
match bounds.height {
|
||||
iced_native::Number::Undefined => f32::INFINITY,
|
||||
iced_native::Number::Defined(h) => h,
|
||||
},
|
||||
);
|
||||
|
||||
let text = Section {
|
||||
text: &content,
|
||||
scale: wgpu_glyph::Scale { x: size, y: size },
|
||||
bounds,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let (width, height) = if let Some(bounds) =
|
||||
glyph_brush.borrow_mut().glyph_bounds(&text)
|
||||
{
|
||||
(bounds.width().ceil(), bounds.height().ceil())
|
||||
} else {
|
||||
(0.0, 0.0)
|
||||
};
|
||||
|
||||
let size = iced_native::Size { width, height };
|
||||
|
||||
// If the text has no width boundary we avoid caching as the
|
||||
// layout engine may just be measuring text in a row.
|
||||
if bounds.0 == f32::INFINITY {
|
||||
return size;
|
||||
} else {
|
||||
*measure = Some(size);
|
||||
}
|
||||
}
|
||||
|
||||
measure.unwrap()
|
||||
})
|
||||
}
|
||||
|
||||
fn draw(&mut self, text: &Text, layout: Layout<'_>) -> Self::Output {
|
||||
(
|
||||
Primitive::Text {
|
||||
content: text.content.clone(),
|
||||
size: f32::from(text.size.unwrap_or(20)),
|
||||
bounds: layout.bounds(),
|
||||
color: text.color.unwrap_or(Color::BLACK),
|
||||
horizontal_alignment: text.horizontal_alignment,
|
||||
vertical_alignment: text.vertical_alignment,
|
||||
},
|
||||
MouseCursor::OutOfBounds,
|
||||
)
|
||||
}
|
||||
}
|
||||
162
wgpu/src/renderer/widget/text_input.rs
Normal file
162
wgpu/src/renderer/widget/text_input.rs
Normal file
|
|
@ -0,0 +1,162 @@
|
|||
use crate::{Primitive, Renderer};
|
||||
|
||||
use iced_native::{
|
||||
text::HorizontalAlignment, text::VerticalAlignment, text_input, Background,
|
||||
Color, MouseCursor, Point, Rectangle, TextInput,
|
||||
};
|
||||
use std::f32;
|
||||
|
||||
impl text_input::Renderer for Renderer {
|
||||
fn default_size(&self) -> u16 {
|
||||
// TODO: Make this configurable
|
||||
20
|
||||
}
|
||||
|
||||
fn draw<Message>(
|
||||
&mut self,
|
||||
text_input: &TextInput<Message>,
|
||||
bounds: Rectangle,
|
||||
text_bounds: Rectangle,
|
||||
cursor_position: Point,
|
||||
) -> Self::Output {
|
||||
let is_mouse_over = bounds.contains(cursor_position);
|
||||
|
||||
let border = Primitive::Quad {
|
||||
bounds,
|
||||
background: Background::Color(
|
||||
if is_mouse_over || text_input.state.is_focused {
|
||||
Color {
|
||||
r: 0.5,
|
||||
g: 0.5,
|
||||
b: 0.5,
|
||||
a: 1.0,
|
||||
}
|
||||
} else {
|
||||
Color {
|
||||
r: 0.7,
|
||||
g: 0.7,
|
||||
b: 0.7,
|
||||
a: 1.0,
|
||||
}
|
||||
},
|
||||
),
|
||||
border_radius: 5,
|
||||
};
|
||||
|
||||
let input = Primitive::Quad {
|
||||
bounds: Rectangle {
|
||||
x: bounds.x + 1.0,
|
||||
y: bounds.y + 1.0,
|
||||
width: bounds.width - 2.0,
|
||||
height: bounds.height - 2.0,
|
||||
},
|
||||
background: Background::Color(Color::WHITE),
|
||||
border_radius: 5,
|
||||
};
|
||||
|
||||
let size = f32::from(text_input.size.unwrap_or(self.default_size()));
|
||||
let text = text_input.value.to_string();
|
||||
|
||||
let value = Primitive::Text {
|
||||
content: if text.is_empty() {
|
||||
text_input.placeholder.clone()
|
||||
} else {
|
||||
text.clone()
|
||||
},
|
||||
color: if text.is_empty() {
|
||||
Color {
|
||||
r: 0.7,
|
||||
g: 0.7,
|
||||
b: 0.7,
|
||||
a: 1.0,
|
||||
}
|
||||
} else {
|
||||
Color {
|
||||
r: 0.3,
|
||||
g: 0.3,
|
||||
b: 0.3,
|
||||
a: 1.0,
|
||||
}
|
||||
},
|
||||
bounds: Rectangle {
|
||||
width: f32::INFINITY,
|
||||
..text_bounds
|
||||
},
|
||||
size,
|
||||
horizontal_alignment: HorizontalAlignment::Left,
|
||||
vertical_alignment: VerticalAlignment::Center,
|
||||
};
|
||||
|
||||
let content = Primitive::Clip {
|
||||
bounds: text_bounds,
|
||||
offset: 0,
|
||||
content: Box::new(if text_input.state.is_focused {
|
||||
use wgpu_glyph::{GlyphCruncher, Scale, Section};
|
||||
|
||||
let text_before_cursor = &text_input
|
||||
.value
|
||||
.until(text_input.state.cursor_position(&text_input.value))
|
||||
.to_string();
|
||||
|
||||
let mut text_value_width = self
|
||||
.glyph_brush
|
||||
.borrow_mut()
|
||||
.glyph_bounds(Section {
|
||||
text: text_before_cursor,
|
||||
bounds: (f32::INFINITY, text_bounds.height),
|
||||
scale: Scale { x: size, y: size },
|
||||
..Default::default()
|
||||
})
|
||||
.map(|bounds| bounds.width().round())
|
||||
.unwrap_or(0.0);
|
||||
|
||||
let spaces_at_the_end = text_before_cursor.len()
|
||||
- text_before_cursor.trim_end().len();
|
||||
|
||||
if spaces_at_the_end > 0 {
|
||||
let space_width = {
|
||||
let glyph_brush = self.glyph_brush.borrow();
|
||||
|
||||
// TODO: Select appropriate font
|
||||
let font = &glyph_brush.fonts()[0];
|
||||
|
||||
font.glyph(' ')
|
||||
.scaled(Scale { x: size, y: size })
|
||||
.h_metrics()
|
||||
.advance_width
|
||||
};
|
||||
|
||||
text_value_width += spaces_at_the_end as f32 * space_width;
|
||||
}
|
||||
|
||||
let cursor = Primitive::Quad {
|
||||
bounds: Rectangle {
|
||||
x: text_bounds.x + text_value_width,
|
||||
y: text_bounds.y,
|
||||
width: 1.0,
|
||||
height: text_bounds.height,
|
||||
},
|
||||
background: Background::Color(Color::BLACK),
|
||||
border_radius: 0,
|
||||
};
|
||||
|
||||
Primitive::Group {
|
||||
primitives: vec![value, cursor],
|
||||
}
|
||||
} else {
|
||||
value
|
||||
}),
|
||||
};
|
||||
|
||||
(
|
||||
Primitive::Group {
|
||||
primitives: vec![border, input, content],
|
||||
},
|
||||
if is_mouse_over {
|
||||
MouseCursor::Text
|
||||
} else {
|
||||
MouseCursor::OutOfBounds
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue