概念
渲染
Druid 底层的窗体渲染使用 piet 库,piet库封装了各平台的GDI 接口,对上层屏蔽每个平台的具体实现。
在Druid中,只要关注它的 PaintCtx
结构与 RenderContext
接口。
context.rs
代码语言:javascript复制pub struct PaintCtx<'a, 'b, 'c> {
pub(crate) state: &'a mut ContextState<'b>,
pub(crate) widget_state: &'a WidgetState,
// 实际渲染使用了 Piet
pub render_ctx: &'a mut Piet<'c>,
// 绘图的顺序
pub(crate) z_ops: Vec<ZOrderPaintOp>,
// 可视区域
pub(crate) region: Region,
// 深度
pub(crate) depth: u32,
}
render_context.rs
代码语言:javascript复制// 删减了部分代码
pub trait RenderContext {
// 状态
fn status(&mut self) -> Result<(), Error>;
// 笔刷
fn solid_brush(&mut self, color: Color) -> Self::Brush;
// 渐变
fn gradient(&mut self, gradient: impl Into<FixedGradient>) -> Result<Self::Brush, Error>;
// 使用指定颜色
fn clear(&mut self, region: impl Into<Option<Rect>>, color: Color);
// 线条
fn stroke(&mut self, shape: impl Shape, brush: &impl IntoBrush<Self>, width: f64);
// 线条样式
fn stroke_styled(
&mut self,
shape: impl Shape,
brush: &impl IntoBrush<Self>,
width: f64,
style: &StrokeStyle,
);
// 使用non-zero规则填充
fn fill(&mut self, shape: impl Shape, brush: &impl IntoBrush<Self>);
// 使用even-odd规则填充
fn fill_even_odd(&mut self, shape: impl Shape, brush: &impl IntoBrush<Self>);
// 遮蔽
fn clip(&mut self, shape: impl Shape);
// 文本
fn text(&mut self) -> &mut Self::Text;
// 绘制文本
fn draw_text(&mut self, layout: &Self::TextLayout, pos: impl Into<Point>);
// 保存状态
fn save(&mut self) -> Result<(), Error>;
// 恢复状态
fn restore(&mut self) -> Result<(), Error>;
// 略
fn with_save(&mut self, f: impl FnOnce(&mut Self);
// 略
fn finish(&mut self) -> Result<(), Error>;
// 矩阵变化
fn transform(&mut self, transform: Affine);
// ... 等
}
上面定义的绘图接口与Canvas的绘图接口非常类似,通过上门的接口, 我们可用实现自定义的一些控件。
图形
Render 接口中需要指定 impl Shape
. Druid 中的2D图形相关定义都在 druid::piet::kurbo 中定义。
具体图形有(翻译了一部分, 具体文档上有详细说明):
Affine | A 2D affine transform. |
---|---|
Arc | 曲线段 |
BezPath | 贝塞尔曲线 |
Circle | 圆形 |
CircleSegment | 圆弧 |
ConstPoint | A trivial "curve" that is just a constant. |
CubicBez | A single cubic Bézier segment. |
CubicBezIter | An iterator for cubic beziers. |
Ellipse | 椭圆 |
Insets | Insets from the edges of a rectangle. |
Line | 线 |
LineIntersection | An intersection of a Line and a PathSeg. |
PathSegIter | An iterator for path segments. |
Point | 点 |
QuadBez | A single quadratic Bézier segment. |
QuadBezIter | An iterator for quadratic beziers. |
Rect | 矩形 |
RoundedRect | A rectangle with equally rounded corners. |
Segments | An iterator that transforms path elements to path segments. |
Size | A 2D size. |
SvgArc | A single SVG arc segment. |
TranslateScale | A transformation including scaling and translation. |
Vec2 | A 2D vector. |
实践
流程:
- 定义一个自绘Widget
- 在paint函数中实现绘制效果
use druid::{piet::StrokeStyle, widget::prelude::*};
use druid::{
kurbo::{Circle, Line},
FontDescriptor, FontFamily, Rect, TextLayout,
};
use druid::{AppLauncher, Color, WindowDesc};
#[derive(Clone, Data)]
struct DrawWidget {
data: f64,
}
impl Widget<String> for DrawWidget {
fn event(&mut self, ctx: &mut EventCtx, event: &Event, data: &mut String, env: &Env) {
}
fn lifecycle(&mut self, ctx: &mut LifeCycleCtx, event: &LifeCycle, data: &String, env: &Env) {
}
fn update(&mut self, ctx: &mut UpdateCtx, old_data: &String, data: &String, env: &Env) {
}
fn layout(
&mut self,
ctx: &mut LayoutCtx,
bc: &BoxConstraints,
data: &String,
env: &Env,
) -> Size {
bc.constrain((200.0, 200.0))
}
fn paint(&mut self, ctx: &mut PaintCtx, data: &String, env: &Env) {
let size = Size::new(40.0, 40.0);
// 矩形
let clip_rect = size.to_rect().inset(-1.0).to_rounded_rect(3.0);
ctx.stroke(clip_rect, &Color::GREEN, 2.0);
// 圆形
let circle = Circle::new((60.0, 20.0), 20.0);
ctx.fill(circle, &Color::YELLOW);
// 椭圆
let rect = Rect::from_origin_size((80., 0.), (80., 40.));
let path = rect.to_ellipse();
ctx.fill(path, &Color::MAROON);
// 线
let path = Line::new((0., 50.), (320., 50.));
let mut stroke_style = StrokeStyle::new();
stroke_style.set_dash_offset(2.0);
ctx.stroke_styled(path, &Color::TEAL, 2.0, &stroke_style);
// 文本
let mut text = TextLayout::new();
text.set_text(data.to_string());
text.set_font(FontDescriptor::new(FontFamily::default()).with_size(24.0));
text.set_text_color(Color::WHITE);
// 涉及到Text属性修改时, 需要调用rebuild方法
text.rebuild_if_needed(ctx.text(), env);
text.draw(ctx, (180., 0.));
// 窗体边框颜色
let border_size = ctx.size();
let border_rect = border_size.to_rect().inset(-1.0).to_rounded_rect(2.0);
ctx.stroke(border_rect, &Color::RED, 2.0);
}
}
pub fn main() {
let window = WindowDesc::new(DrawWidget { data: 0.0 }).window_size((400., 400.));
AppLauncher::with_window(window)
.log_to_console()
.launch("Hello Druid ;)".to_string())
.unwrap();
}