Add border_width and border_color to Quad

This commit is contained in:
Héctor Ramón Jiménez 2019-12-31 21:35:42 +01:00
parent 649d72e7de
commit 9ab7c47dc7
17 changed files with 180 additions and 128 deletions

View File

@ -25,6 +25,14 @@ impl Color {
a: 1.0, a: 1.0,
}; };
/// A color with no opacity.
pub const TRANSPARENT: Color = Color {
r: 0.0,
g: 0.0,
b: 0.0,
a: 0.0,
};
/// Creates a [`Color`] from its RGB8 components. /// Creates a [`Color`] from its RGB8 components.
/// ///
/// [`Color`]: struct.Color.html /// [`Color`]: struct.Color.html

View File

@ -63,6 +63,8 @@ mod circle {
bounds: layout.bounds(), bounds: layout.bounds(),
background: Background::Color(Color::BLACK), background: Background::Color(Color::BLACK),
border_radius: self.radius, border_radius: self.radius,
border_width: 0,
border_color: Color::TRANSPARENT,
}, },
MouseCursor::OutOfBounds, MouseCursor::OutOfBounds,
) )

View File

@ -27,7 +27,7 @@ impl Sandbox for Tour {
scroll: scrollable::State::new(), scroll: scrollable::State::new(),
back_button: button::State::new(), back_button: button::State::new(),
next_button: button::State::new(), next_button: button::State::new(),
debug: false, debug: true,
} }
} }

View File

@ -38,6 +38,10 @@ pub enum Primitive {
background: Background, background: Background,
/// The border radius of the quad /// The border radius of the quad
border_radius: u16, border_radius: u16,
/// The border width of the quad
border_width: u16,
/// The border color of the quad
border_color: Color,
}, },
/// An image primitive /// An image primitive
Image { Image {

View File

@ -125,9 +125,19 @@ impl Pipeline {
}, },
wgpu::VertexAttributeDescriptor { wgpu::VertexAttributeDescriptor {
shader_location: 4, shader_location: 4,
format: wgpu::VertexFormat::Float, format: wgpu::VertexFormat::Float4,
offset: 4 * (2 + 2 + 4), offset: 4 * (2 + 2 + 4),
}, },
wgpu::VertexAttributeDescriptor {
shader_location: 5,
format: wgpu::VertexFormat::Float,
offset: 4 * (2 + 2 + 4 + 4),
},
wgpu::VertexAttributeDescriptor {
shader_location: 6,
format: wgpu::VertexFormat::Float,
offset: 4 * (2 + 2 + 4 + 4 + 1),
},
], ],
}, },
], ],
@ -233,7 +243,8 @@ impl Pipeline {
bounds.x, bounds.x,
bounds.y, bounds.y,
bounds.width, bounds.width,
bounds.height, // TODO: Address anti-aliasing adjustments properly
bounds.height + 1,
); );
render_pass.draw_indexed( render_pass.draw_indexed(
@ -277,7 +288,9 @@ pub struct Quad {
pub position: [f32; 2], pub position: [f32; 2],
pub scale: [f32; 2], pub scale: [f32; 2],
pub color: [f32; 4], pub color: [f32; 4],
pub border_color: [f32; 4],
pub border_radius: f32, pub border_radius: f32,
pub border_width: f32,
} }
impl Quad { impl Quad {

View File

@ -223,6 +223,8 @@ impl Renderer {
bounds, bounds,
background, background,
border_radius, border_radius,
border_width,
border_color,
} => { } => {
// TODO: Move some of this computations to the GPU (?) // TODO: Move some of this computations to the GPU (?)
layer.quads.push(Quad { layer.quads.push(Quad {
@ -235,6 +237,8 @@ impl Renderer {
Background::Color(color) => color.into_linear(), Background::Color(color) => color.into_linear(),
}, },
border_radius: *border_radius as f32, border_radius: *border_radius as f32,
border_width: *border_width as f32,
border_color: border_color.into_linear(),
}); });
} }
Primitive::Image { handle, bounds } => { Primitive::Image { handle, bounds } => {
@ -470,11 +474,12 @@ fn explain_layout(
color: Color, color: Color,
primitives: &mut Vec<Primitive>, primitives: &mut Vec<Primitive>,
) { ) {
// TODO: Draw borders instead
primitives.push(Primitive::Quad { primitives.push(Primitive::Quad {
bounds: layout.bounds(), bounds: layout.bounds(),
background: Background::Color([0.0, 0.0, 0.0, 0.05].into()), background: Background::Color(Color::TRANSPARENT),
border_radius: 0, border_radius: 0,
border_width: 1,
border_color: [0.6, 0.6, 0.6, 0.5].into(),
}); });
for child in layout.children() { for child in layout.children() {

View File

@ -1,5 +1,7 @@
use crate::{button::StyleSheet, defaults, Defaults, Primitive, Renderer}; use crate::{button::StyleSheet, defaults, Defaults, Primitive, Renderer};
use iced_native::{Background, Element, Layout, MouseCursor, Point, Rectangle}; use iced_native::{
Background, Color, Element, Layout, MouseCursor, Point, Rectangle,
};
impl iced_native::button::Renderer for Renderer { impl iced_native::button::Renderer for Renderer {
type Style = Box<dyn StyleSheet>; type Style = Box<dyn StyleSheet>;
@ -57,11 +59,15 @@ impl iced_native::button::Renderer for Renderer {
[0.0, 0.0, 0.0, 0.5].into(), [0.0, 0.0, 0.0, 0.5].into(),
), ),
border_radius: styling.border_radius, border_radius: styling.border_radius,
border_width: 0,
border_color: Color::TRANSPARENT,
}, },
Primitive::Quad { Primitive::Quad {
bounds, bounds,
background, background,
border_radius: styling.border_radius, border_radius: styling.border_radius,
border_width: 0,
border_color: Color::TRANSPARENT,
}, },
content, content,
], ],

View File

@ -1,6 +1,6 @@
use crate::{Primitive, Renderer}; use crate::{Primitive, Renderer};
use iced_native::{ use iced_native::{
checkbox, Background, HorizontalAlignment, MouseCursor, Rectangle, checkbox, Background, Color, HorizontalAlignment, MouseCursor, Rectangle,
VerticalAlignment, VerticalAlignment,
}; };
@ -18,30 +18,20 @@ impl checkbox::Renderer for Renderer {
is_mouse_over: bool, is_mouse_over: bool,
(label, _): Self::Output, (label, _): Self::Output,
) -> Self::Output { ) -> Self::Output {
let (checkbox_border, checkbox_box) = ( let checkbox = Primitive::Quad {
Primitive::Quad { bounds,
bounds, background: Background::Color(
background: Background::Color([0.6, 0.6, 0.6].into()), if is_mouse_over {
border_radius: 6, [0.90, 0.90, 0.90]
}, } else {
Primitive::Quad { [0.95, 0.95, 0.95]
bounds: Rectangle { }
x: bounds.x + 1.0, .into(),
y: bounds.y + 1.0, ),
width: bounds.width - 2.0, border_radius: 5,
height: bounds.height - 2.0, border_width: 1,
}, border_color: Color::from_rgb(0.6, 0.6, 0.6),
background: Background::Color( };
if is_mouse_over {
[0.90, 0.90, 0.90]
} else {
[0.95, 0.95, 0.95]
}
.into(),
),
border_radius: 5,
},
);
( (
Primitive::Group { Primitive::Group {
@ -56,9 +46,9 @@ impl checkbox::Renderer for Renderer {
vertical_alignment: VerticalAlignment::Center, vertical_alignment: VerticalAlignment::Center,
}; };
vec![checkbox_border, checkbox_box, check, label] vec![checkbox, check, label]
} else { } else {
vec![checkbox_border, checkbox_box, label] vec![checkbox, label]
}, },
}, },
if is_mouse_over { if is_mouse_over {

View File

@ -1,5 +1,5 @@
use crate::{container, defaults, Defaults, Primitive, Renderer}; use crate::{container, defaults, Defaults, Primitive, Renderer};
use iced_native::{Element, Layout, Point, Rectangle}; use iced_native::{Color, Element, Layout, Point, Rectangle};
impl iced_native::container::Renderer for Renderer { impl iced_native::container::Renderer for Renderer {
type Style = Box<dyn container::StyleSheet>; type Style = Box<dyn container::StyleSheet>;
@ -31,6 +31,8 @@ impl iced_native::container::Renderer for Renderer {
bounds, bounds,
background, background,
border_radius: style.border_radius, border_radius: style.border_radius,
border_width: 0,
border_color: Color::TRANSPARENT,
}; };
( (

View File

@ -1,5 +1,5 @@
use crate::{Primitive, Renderer}; use crate::{Primitive, Renderer};
use iced_native::{radio, Background, MouseCursor, Rectangle}; use iced_native::{radio, Background, Color, MouseCursor, Rectangle};
const SIZE: f32 = 28.0; const SIZE: f32 = 28.0;
const DOT_SIZE: f32 = SIZE / 2.0; const DOT_SIZE: f32 = SIZE / 2.0;
@ -16,30 +16,20 @@ impl radio::Renderer for Renderer {
is_mouse_over: bool, is_mouse_over: bool,
(label, _): Self::Output, (label, _): Self::Output,
) -> Self::Output { ) -> Self::Output {
let (radio_border, radio_box) = ( let radio = Primitive::Quad {
Primitive::Quad { bounds,
bounds, background: Background::Color(
background: Background::Color([0.6, 0.6, 0.6].into()), if is_mouse_over {
border_radius: (SIZE / 2.0) as u16, [0.90, 0.90, 0.90]
}, } else {
Primitive::Quad { [0.95, 0.95, 0.95]
bounds: Rectangle { }
x: bounds.x + 1.0, .into(),
y: bounds.y + 1.0, ),
width: bounds.width - 2.0, border_radius: (SIZE / 2.0) as u16,
height: bounds.height - 2.0, border_width: 1,
}, border_color: Color::from_rgb(0.6, 0.6, 0.6),
background: Background::Color( };
if is_mouse_over {
[0.90, 0.90, 0.90]
} else {
[0.95, 0.95, 0.95]
}
.into(),
),
border_radius: (SIZE / 2.0 - 1.0) as u16,
},
);
( (
Primitive::Group { Primitive::Group {
@ -53,11 +43,13 @@ impl radio::Renderer for Renderer {
}, },
background: Background::Color([0.3, 0.3, 0.3].into()), background: Background::Color([0.3, 0.3, 0.3].into()),
border_radius: (DOT_SIZE / 2.0) as u16, border_radius: (DOT_SIZE / 2.0) as u16,
border_width: 0,
border_color: Color::TRANSPARENT,
}; };
vec![radio_border, radio_box, radio_circle, label] vec![radio, radio_circle, label]
} else { } else {
vec![radio_border, radio_box, label] vec![radio, label]
}, },
}, },
if is_mouse_over { if is_mouse_over {

View File

@ -1,5 +1,7 @@
use crate::{Primitive, Renderer}; use crate::{Primitive, Renderer};
use iced_native::{scrollable, Background, MouseCursor, Rectangle, Vector}; use iced_native::{
scrollable, Background, Color, MouseCursor, Rectangle, Vector,
};
const SCROLLBAR_WIDTH: u16 = 10; const SCROLLBAR_WIDTH: u16 = 10;
const SCROLLBAR_MARGIN: u16 = 2; const SCROLLBAR_MARGIN: u16 = 2;
@ -68,6 +70,8 @@ impl scrollable::Renderer for Renderer {
[0.0, 0.0, 0.0, 0.7].into(), [0.0, 0.0, 0.0, 0.7].into(),
), ),
border_radius: 5, border_radius: 5,
border_width: 0,
border_color: Color::TRANSPARENT,
}; };
if is_mouse_over_scrollbar || state.is_scroller_grabbed() { if is_mouse_over_scrollbar || state.is_scroller_grabbed() {
@ -83,6 +87,8 @@ impl scrollable::Renderer for Renderer {
[0.0, 0.0, 0.0, 0.3].into(), [0.0, 0.0, 0.0, 0.3].into(),
), ),
border_radius: 5, border_radius: 5,
border_width: 0,
border_color: Color::TRANSPARENT,
}; };
Primitive::Group { Primitive::Group {

View File

@ -29,8 +29,10 @@ impl slider::Renderer for Renderer {
width: bounds.width, width: bounds.width,
height: 2.0, height: 2.0,
}, },
background: Color::from_rgb(0.6, 0.6, 0.6).into(), background: Background::Color([0.6, 0.6, 0.6, 0.5].into()),
border_radius: 0, border_radius: 0,
border_width: 0,
border_color: Color::TRANSPARENT,
}, },
Primitive::Quad { Primitive::Quad {
bounds: Rectangle { bounds: Rectangle {
@ -41,6 +43,8 @@ impl slider::Renderer for Renderer {
}, },
background: Background::Color(Color::WHITE), background: Background::Color(Color::WHITE),
border_radius: 0, border_radius: 0,
border_width: 0,
border_color: Color::TRANSPARENT,
}, },
); );
@ -49,41 +53,31 @@ impl slider::Renderer for Renderer {
let handle_offset = (bounds.width - HANDLE_WIDTH) let handle_offset = (bounds.width - HANDLE_WIDTH)
* ((value - range_start) / (range_end - range_start).max(1.0)); * ((value - range_start) / (range_end - range_start).max(1.0));
let (handle_border, handle) = ( let handle = Primitive::Quad {
Primitive::Quad { bounds: Rectangle {
bounds: Rectangle { x: bounds.x + handle_offset.round(),
x: bounds.x + handle_offset.round() - 1.0, y: rail_y - HANDLE_HEIGHT / 2.0,
y: rail_y - HANDLE_HEIGHT / 2.0 - 1.0, width: HANDLE_WIDTH,
width: HANDLE_WIDTH + 2.0, height: HANDLE_HEIGHT,
height: HANDLE_HEIGHT + 2.0,
},
background: Color::from_rgb(0.6, 0.6, 0.6).into(),
border_radius: 5,
}, },
Primitive::Quad { background: Background::Color(
bounds: Rectangle { if is_dragging {
x: bounds.x + handle_offset.round(), [0.85, 0.85, 0.85]
y: rail_y - HANDLE_HEIGHT / 2.0, } else if is_mouse_over {
width: HANDLE_WIDTH, [0.90, 0.90, 0.90]
height: HANDLE_HEIGHT, } else {
}, [0.95, 0.95, 0.95]
background: Background::Color( }
if is_dragging { .into(),
[0.85, 0.85, 0.85] ),
} else if is_mouse_over { border_radius: 4,
[0.90, 0.90, 0.90] border_width: 1,
} else { border_color: Color::from_rgb(0.6, 0.6, 0.6),
[0.95, 0.95, 0.95] };
}
.into(),
),
border_radius: 4,
},
);
( (
Primitive::Group { Primitive::Group {
primitives: vec![rail_top, rail_bottom, handle_border, handle], primitives: vec![rail_top, rail_bottom, handle],
}, },
if is_dragging { if is_dragging {
MouseCursor::Grabbing MouseCursor::Grabbing

View File

@ -64,28 +64,17 @@ impl text_input::Renderer for Renderer {
) -> Self::Output { ) -> Self::Output {
let is_mouse_over = bounds.contains(cursor_position); let is_mouse_over = bounds.contains(cursor_position);
let border = Primitive::Quad {
bounds,
background: Background::Color(
if is_mouse_over || state.is_focused() {
[0.5, 0.5, 0.5]
} else {
[0.7, 0.7, 0.7]
}
.into(),
),
border_radius: 5,
};
let input = Primitive::Quad { let input = Primitive::Quad {
bounds: Rectangle { bounds,
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), background: Background::Color(Color::WHITE),
border_radius: 4, border_radius: 5,
border_width: 1,
border_color: if is_mouse_over || state.is_focused() {
[0.5, 0.5, 0.5]
} else {
[0.7, 0.7, 0.7]
}
.into(),
}; };
let text = value.to_string(); let text = value.to_string();
@ -130,6 +119,8 @@ impl text_input::Renderer for Renderer {
}, },
background: Background::Color(Color::BLACK), background: Background::Color(Color::BLACK),
border_radius: 0, border_radius: 0,
border_width: 0,
border_color: Color::TRANSPARENT,
}; };
( (
@ -150,7 +141,7 @@ impl text_input::Renderer for Renderer {
( (
Primitive::Group { Primitive::Group {
primitives: vec![border, input, contents], primitives: vec![input, contents],
}, },
if is_mouse_over { if is_mouse_over {
MouseCursor::Text MouseCursor::Text

View File

@ -1,14 +1,17 @@
#version 450 #version 450
layout(location = 0) in vec4 v_Color; layout(location = 0) in vec4 v_Color;
layout(location = 1) in vec2 v_Pos; layout(location = 1) in vec4 v_BorderColor;
layout(location = 2) in vec2 v_Scale; layout(location = 2) in vec2 v_Pos;
layout(location = 3) in float v_BorderRadius; layout(location = 3) in vec2 v_Scale;
layout(location = 4) in float v_BorderRadius;
layout(location = 5) in float v_BorderWidth;
layout(location = 0) out vec4 o_Color; layout(location = 0) out vec4 o_Color;
float rounded(in vec2 frag_coord, in vec2 position, in vec2 size, float radius, float s) float distance(in vec2 frag_coord, in vec2 position, in vec2 size, float radius)
{ {
// TODO: Try SDF approach: https://www.shadertoy.com/view/wd3XRN
vec2 inner_size = size - vec2(radius, radius) * 2.0; vec2 inner_size = size - vec2(radius, radius) * 2.0;
vec2 top_left = position + vec2(radius, radius); vec2 top_left = position + vec2(radius, radius);
vec2 bottom_right = top_left + inner_size; vec2 bottom_right = top_left + inner_size;
@ -21,13 +24,43 @@ float rounded(in vec2 frag_coord, in vec2 position, in vec2 size, float radius,
max(max(top_left_distance.y, bottom_right_distance.y), 0) max(max(top_left_distance.y, bottom_right_distance.y), 0)
); );
float d = sqrt(distance.x * distance.x + distance.y * distance.y); return sqrt(distance.x * distance.x + distance.y * distance.y);
return 1.0 - smoothstep(radius - s, radius + s, d);
} }
void main() { void main() {
float radius_alpha = rounded(gl_FragCoord.xy, v_Pos, v_Scale, v_BorderRadius, 0.5); vec4 mixed_color;
o_Color = vec4(v_Color.xyz, v_Color.w * radius_alpha); // TODO: Remove branching (?)
if(v_BorderWidth > 0) {
float internal_border = max(v_BorderRadius - v_BorderWidth, 0);
float internal_distance = distance(
gl_FragCoord.xy,
v_Pos + vec2(v_BorderWidth),
v_Scale - vec2(v_BorderWidth * 2.0),
internal_border
);
float border_mix = smoothstep(
max(internal_border - 0.5, 0.0),
internal_border + 0.5,
internal_distance
);
mixed_color = mix(v_Color, v_BorderColor, border_mix);
} else {
mixed_color = v_Color;
}
float d = distance(
gl_FragCoord.xy,
v_Pos,
v_Scale,
v_BorderRadius
);
float radius_alpha =
1.0 - smoothstep(max(v_BorderRadius - 0.5, 0), v_BorderRadius + 0.5, d);
o_Color = vec4(mixed_color.xyz, mixed_color.w * radius_alpha);
} }

Binary file not shown.

View File

@ -4,7 +4,9 @@ layout(location = 0) in vec2 v_Pos;
layout(location = 1) in vec2 i_Pos; layout(location = 1) in vec2 i_Pos;
layout(location = 2) in vec2 i_Scale; layout(location = 2) in vec2 i_Scale;
layout(location = 3) in vec4 i_Color; layout(location = 3) in vec4 i_Color;
layout(location = 4) in float i_BorderRadius; layout(location = 4) in vec4 i_BorderColor;
layout(location = 5) in float i_BorderRadius;
layout(location = 6) in float i_BorderWidth;
layout (set = 0, binding = 0) uniform Globals { layout (set = 0, binding = 0) uniform Globals {
mat4 u_Transform; mat4 u_Transform;
@ -12,9 +14,11 @@ layout (set = 0, binding = 0) uniform Globals {
}; };
layout(location = 0) out vec4 o_Color; layout(location = 0) out vec4 o_Color;
layout(location = 1) out vec2 o_Pos; layout(location = 1) out vec4 o_BorderColor;
layout(location = 2) out vec2 o_Scale; layout(location = 2) out vec2 o_Pos;
layout(location = 3) out float o_BorderRadius; layout(location = 3) out vec2 o_Scale;
layout(location = 4) out float o_BorderRadius;
layout(location = 5) out float o_BorderWidth;
void main() { void main() {
vec2 p_Pos = i_Pos * u_Scale; vec2 p_Pos = i_Pos * u_Scale;
@ -28,9 +32,11 @@ void main() {
); );
o_Color = i_Color; o_Color = i_Color;
o_BorderColor = i_BorderColor;
o_Pos = p_Pos; o_Pos = p_Pos;
o_Scale = p_Scale; o_Scale = p_Scale;
o_BorderRadius = i_BorderRadius * u_Scale; o_BorderRadius = i_BorderRadius * u_Scale;
o_BorderWidth = i_BorderWidth * u_Scale;
gl_Position = u_Transform * i_Transform * vec4(v_Pos, 0.0, 1.0); gl_Position = u_Transform * i_Transform * vec4(v_Pos, 0.0, 1.0);
} }

Binary file not shown.