mirror of https://github.com/linebender/xilem
170 lines
6.3 KiB
Rust
170 lines
6.3 KiB
Rust
// Copyright 2019 the Xilem Authors and the Druid Authors
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
//! An example of a custom drawing widget.
|
|
//! We draw an image, some text, a shape, and a curve.
|
|
|
|
// On Windows platform, don't show a console when opening the app.
|
|
#![windows_subsystem = "windows"]
|
|
|
|
use accesskit::{NodeBuilder, Role};
|
|
use masonry::kurbo::{BezPath, Stroke};
|
|
use masonry::widget::{ObjectFit, RootWidget};
|
|
use masonry::{
|
|
AccessCtx, AccessEvent, Action, Affine, AppDriver, BoxConstraints, Color, DriverCtx, EventCtx,
|
|
LayoutCtx, PaintCtx, Point, PointerEvent, Rect, RegisterCtx, Size, TextEvent, Widget, WidgetId,
|
|
};
|
|
use parley::layout::Alignment;
|
|
use parley::style::{FontFamily, FontStack, StyleProperty};
|
|
use smallvec::SmallVec;
|
|
use tracing::{trace_span, Span};
|
|
use vello::peniko::{Brush, Fill, Format, Image};
|
|
use vello::Scene;
|
|
use winit::window::Window;
|
|
|
|
struct Driver;
|
|
|
|
impl AppDriver for Driver {
|
|
fn on_action(&mut self, _ctx: &mut DriverCtx<'_>, _widget_id: WidgetId, _action: Action) {}
|
|
}
|
|
|
|
struct CustomWidget(String);
|
|
|
|
impl Widget for CustomWidget {
|
|
fn on_pointer_event(&mut self, _ctx: &mut EventCtx, _event: &PointerEvent) {}
|
|
|
|
fn on_text_event(&mut self, _ctx: &mut EventCtx, _event: &TextEvent) {}
|
|
|
|
fn on_access_event(&mut self, _ctx: &mut EventCtx, _event: &AccessEvent) {}
|
|
|
|
fn register_children(&mut self, _ctx: &mut RegisterCtx) {}
|
|
|
|
fn layout(&mut self, _layout_ctx: &mut LayoutCtx, bc: &BoxConstraints) -> Size {
|
|
// BoxConstraints are passed by the parent widget.
|
|
// This method can return any Size within those constraints:
|
|
// bc.constrain(my_size)
|
|
//
|
|
// To check if a dimension is infinite or not (e.g. scrolling):
|
|
// bc.is_width_bounded() / bc.is_height_bounded()
|
|
//
|
|
// bx.max() returns the maximum size of the widget. Be careful
|
|
// using this, since always make sure the widget is bounded.
|
|
// If bx.max() is used in a scrolling widget things will probably
|
|
// not work correctly.
|
|
if bc.is_width_bounded() && bc.is_height_bounded() {
|
|
bc.max()
|
|
} else {
|
|
let size = Size::new(100.0, 100.0);
|
|
bc.constrain(size)
|
|
}
|
|
}
|
|
|
|
// The paint method gets called last, after an event flow.
|
|
// It goes event -> update -> layout -> paint, and each method can influence the next.
|
|
// Basically, anything that changes the appearance of a widget causes a paint.
|
|
fn paint(&mut self, ctx: &mut PaintCtx, scene: &mut Scene) {
|
|
// Clear the whole widget with the color of your choice
|
|
// (ctx.size() returns the size of the layout rect we're painting in)
|
|
// Note: ctx also has a `clear` method, but that clears the whole context,
|
|
// and we only want to clear this widget's area.
|
|
let size = ctx.size();
|
|
let rect = size.to_rect();
|
|
scene.fill(Fill::NonZero, Affine::IDENTITY, Color::WHITE, None, &rect);
|
|
|
|
// Create an arbitrary bezier path
|
|
let mut path = BezPath::new();
|
|
path.move_to(Point::ORIGIN);
|
|
path.quad_to((60.0, 120.0), (size.width, size.height));
|
|
// Create a color
|
|
let stroke_color = Color::rgb8(0, 128, 0);
|
|
// Stroke the path with thickness 5.0
|
|
scene.stroke(
|
|
&Stroke::new(5.0),
|
|
Affine::IDENTITY,
|
|
stroke_color,
|
|
None,
|
|
&path,
|
|
);
|
|
|
|
// Rectangles: the path for practical people
|
|
let rect = Rect::from_origin_size((10.0, 10.0), (100.0, 100.0));
|
|
// Note the Color:rgba8 which includes an alpha channel (7F in this case)
|
|
let fill_color = Color::rgba8(0x00, 0x00, 0x00, 0x7F);
|
|
scene.fill(Fill::NonZero, Affine::IDENTITY, fill_color, None, &rect);
|
|
|
|
// To render text, we first create a LayoutBuilder and set the text properties.
|
|
let mut lcx = parley::LayoutContext::new();
|
|
let mut text_layout_builder = lcx.ranged_builder(ctx.text_contexts().0, &self.0, 1.0);
|
|
|
|
text_layout_builder.push_default(&StyleProperty::FontStack(FontStack::Single(
|
|
FontFamily::Generic(parley::style::GenericFamily::Serif),
|
|
)));
|
|
text_layout_builder.push_default(&StyleProperty::FontSize(24.0));
|
|
text_layout_builder.push_default(&StyleProperty::Brush(Brush::Solid(fill_color).into()));
|
|
|
|
let mut text_layout = text_layout_builder.build();
|
|
text_layout.break_all_lines(None, Alignment::Start);
|
|
|
|
let mut scratch_scene = Scene::new();
|
|
// We can pass a transform matrix to rotate the text we render
|
|
masonry::text::render_text(
|
|
scene,
|
|
&mut scratch_scene,
|
|
Affine::rotate(std::f64::consts::FRAC_PI_4).then_translate((80.0, 40.0).into()),
|
|
&text_layout,
|
|
);
|
|
|
|
// Let's burn some CPU to make a (partially transparent) image buffer
|
|
let image_data = make_image_data(256, 256);
|
|
let image_data = Image::new(image_data.into(), Format::Rgba8, 256, 256);
|
|
let transform = ObjectFit::Fill.affine_to_fill(ctx.size(), size);
|
|
scene.draw_image(&image_data, transform);
|
|
}
|
|
|
|
fn accessibility_role(&self) -> Role {
|
|
Role::Window
|
|
}
|
|
|
|
fn accessibility(&mut self, _ctx: &mut AccessCtx, node: &mut NodeBuilder) {
|
|
let text = &self.0;
|
|
node.set_name(
|
|
format!("This is a demo of the Masonry Widget trait. Masonry has accessibility tree support. The demo shows colored shapes with the text '{text}'."),
|
|
);
|
|
}
|
|
|
|
fn children_ids(&self) -> SmallVec<[WidgetId; 16]> {
|
|
SmallVec::new()
|
|
}
|
|
|
|
fn make_trace_span(&self) -> Span {
|
|
trace_span!("CustomWidget")
|
|
}
|
|
}
|
|
|
|
pub fn main() {
|
|
let my_string = "Masonry + Vello".to_string();
|
|
let window_attributes = Window::default_attributes().with_title("Fancy colors");
|
|
|
|
masonry::event_loop_runner::run(
|
|
masonry::event_loop_runner::EventLoop::with_user_event(),
|
|
window_attributes,
|
|
RootWidget::new(CustomWidget(my_string)),
|
|
Driver,
|
|
)
|
|
.unwrap();
|
|
}
|
|
|
|
fn make_image_data(width: usize, height: usize) -> Vec<u8> {
|
|
let mut result = vec![0; width * height * 4];
|
|
for y in 0..height {
|
|
for x in 0..width {
|
|
let ix = (y * width + x) * 4;
|
|
result[ix] = x as u8;
|
|
result[ix + 1] = y as u8;
|
|
result[ix + 2] = !(x as u8);
|
|
result[ix + 3] = 127;
|
|
}
|
|
}
|
|
result
|
|
}
|