mirror of https://github.com/linebender/xilem
Merge pull request #1 from linebender/new_infra
Work in progress xilem prototype
This commit is contained in:
commit
ef1d90732b
File diff suppressed because it is too large
Load Diff
36
Cargo.toml
36
Cargo.toml
|
@ -1,16 +1,32 @@
|
|||
[package]
|
||||
name = "xilem"
|
||||
version = "0.1.0"
|
||||
license = "Apache-2.0"
|
||||
authors = ["Raph Levien <raph@google.com>"]
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
"druid-shell" = "0.7"
|
||||
bitflags = "1.3.2"
|
||||
futures-task = "0.3"
|
||||
tokio = { version = "1", features = ["full"] }
|
||||
# This is just a bin for now but will be a lib + examples soon
|
||||
|
||||
[dev-dependencies]
|
||||
sha2 = "0.10"
|
||||
hex = "0.4.3"
|
||||
[features]
|
||||
default = ["x11"]
|
||||
|
||||
gtk = ["glazier/gtk"]
|
||||
x11 = ["glazier/x11"]
|
||||
wayland = ["glazier/wayland"]
|
||||
|
||||
[dependencies]
|
||||
glazier = { git = "https://github.com/linebender/glazier", default-features = false }
|
||||
piet-gpu-hal = { git = "https://github.com/linebender/piet-gpu" }
|
||||
piet-gpu = { git = "https://github.com/linebender/piet-gpu" }
|
||||
piet-scene = { git = "https://github.com/linebender/piet-gpu", features = ["kurbo"] }
|
||||
raw-window-handle = "0.5"
|
||||
png = "0.16.2"
|
||||
rand = "0.7.3"
|
||||
roxmltree = "0.13"
|
||||
swash = "0.1.4"
|
||||
bytemuck = { version = "1.7.2", features = ["derive"] }
|
||||
parley = { git = "https://github.com/dfrg/parley" }
|
||||
tokio = { version = "1.21", features = ["full"] }
|
||||
futures-task = "0.3"
|
||||
bitflags = "1.3.2"
|
||||
|
||||
[patch."https://github.com/dfrg/fount"]
|
||||
fount = { git = "https://github.com/jneem/fount" }
|
||||
|
|
38
src/app.rs
38
src/app.rs
|
@ -16,14 +16,14 @@ use std::collections::HashSet;
|
|||
use std::sync::{Arc, Mutex};
|
||||
use std::time::Duration;
|
||||
|
||||
use druid_shell::kurbo::Size;
|
||||
use druid_shell::piet::{Color, Piet, RenderContext};
|
||||
use druid_shell::{IdleHandle, IdleToken, WindowHandle};
|
||||
use glazier::kurbo::Size;
|
||||
use glazier::{IdleHandle, IdleToken, WindowHandle};
|
||||
use parley::FontContext;
|
||||
use tokio::runtime::Runtime;
|
||||
|
||||
use crate::event::{AsyncWake, EventResult};
|
||||
use crate::id::IdPath;
|
||||
use crate::widget::{CxState, EventCx, LayoutCx, PaintCx, Pod, UpdateCx, WidgetState};
|
||||
use crate::widget::{CxState, EventCx, LayoutCx, PaintCx, Pod, Rendered, UpdateCx, WidgetState};
|
||||
use crate::{
|
||||
event::Event,
|
||||
id::Id,
|
||||
|
@ -42,6 +42,7 @@ pub struct App<T, V: View<T>> {
|
|||
root_pod: Option<Pod>,
|
||||
size: Size,
|
||||
cx: Cx,
|
||||
font_cx: FontContext,
|
||||
pub(crate) rt: Runtime,
|
||||
}
|
||||
|
||||
|
@ -92,8 +93,6 @@ enum UiState {
|
|||
#[derive(Clone, Default)]
|
||||
pub struct WakeQueue(Arc<Mutex<Vec<IdPath>>>);
|
||||
|
||||
const BG_COLOR: Color = Color::rgb8(0x27, 0x28, 0x22);
|
||||
|
||||
impl<T: Send + 'static, V: View<T> + 'static> App<T, V>
|
||||
where
|
||||
V::Element: Widget + 'static,
|
||||
|
@ -153,6 +152,7 @@ where
|
|||
root_state: Default::default(),
|
||||
size: Default::default(),
|
||||
cx,
|
||||
font_cx: FontContext::new(),
|
||||
rt,
|
||||
}
|
||||
}
|
||||
|
@ -170,15 +170,14 @@ where
|
|||
self.size = size;
|
||||
}
|
||||
|
||||
pub fn paint(&mut self, piet: &mut Piet) {
|
||||
let rect = self.size.to_rect();
|
||||
piet.fill(rect, &BG_COLOR);
|
||||
|
||||
pub fn paint(&mut self) -> Rendered {
|
||||
loop {
|
||||
self.send_events();
|
||||
// TODO: be more lazy re-rendering
|
||||
self.render();
|
||||
let root_pod = self.root_pod.as_mut().unwrap();
|
||||
let mut cx_state = CxState::new(&self.window_handle, &mut self.events);
|
||||
let mut cx_state =
|
||||
CxState::new(&self.window_handle, &mut self.font_cx, &mut self.events);
|
||||
let mut update_cx = UpdateCx::new(&mut cx_state, &mut self.root_state);
|
||||
root_pod.update(&mut update_cx);
|
||||
let mut layout_cx = LayoutCx::new(&mut cx_state, &mut self.root_state);
|
||||
|
@ -198,15 +197,15 @@ where
|
|||
// Rerun app logic, primarily for virtualized scrolling
|
||||
continue;
|
||||
}
|
||||
let mut paint_cx = PaintCx::new(&mut cx_state, &mut self.root_state, piet);
|
||||
root_pod.paint(&mut paint_cx);
|
||||
break;
|
||||
let mut paint_cx = PaintCx::new(&mut cx_state, &mut self.root_state);
|
||||
return root_pod.paint(&mut paint_cx);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn window_event(&mut self, event: RawEvent) {
|
||||
self.ensure_root();
|
||||
let root_pod = self.root_pod.as_mut().unwrap();
|
||||
let mut cx_state = CxState::new(&self.window_handle, &mut self.events);
|
||||
let mut cx_state = CxState::new(&self.window_handle, &mut self.font_cx, &mut self.events);
|
||||
let mut event_cx = EventCx::new(&mut cx_state, &mut self.root_state);
|
||||
root_pod.event(&mut event_cx, &event);
|
||||
self.send_events();
|
||||
|
@ -219,6 +218,13 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
// Make sure the widget tree (root pod) is available
|
||||
fn ensure_root(&mut self) {
|
||||
if self.root_pod.is_none() {
|
||||
self.render();
|
||||
}
|
||||
}
|
||||
|
||||
/// Run the app logic and update the widget tree.
|
||||
fn render(&mut self) {
|
||||
if self.render_inner(false) {
|
||||
|
@ -325,7 +331,7 @@ where
|
|||
self.ui_state = UiState::Delayed;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
Ok(None) => break,
|
||||
Err(_) => {
|
||||
self.render().await;
|
||||
|
|
|
@ -14,10 +14,12 @@
|
|||
|
||||
use std::any::Any;
|
||||
|
||||
use druid_shell::{
|
||||
kurbo::Size, Application, Cursor, HotKey, IdleToken, Menu, MouseEvent, Region, SysMods,
|
||||
WinHandler, WindowBuilder, WindowHandle,
|
||||
use glazier::{
|
||||
kurbo::Size, Application, Cursor, HotKey, IdleToken, Menu, MouseEvent, Region, Scalable,
|
||||
SysMods, WinHandler, WindowBuilder, WindowHandle,
|
||||
};
|
||||
use parley::FontContext;
|
||||
use piet_scene::{Scene, SceneBuilder, SceneFragment};
|
||||
|
||||
use crate::{app::App, widget::RawEvent, View, Widget};
|
||||
|
||||
|
@ -35,6 +37,10 @@ where
|
|||
{
|
||||
handle: WindowHandle,
|
||||
app: App<T, V>,
|
||||
pgpu_state: Option<crate::render::PgpuState>,
|
||||
font_context: FontContext,
|
||||
scene: Scene,
|
||||
counter: u64,
|
||||
}
|
||||
|
||||
const QUIT_MENU_ID: u32 = 0x100;
|
||||
|
@ -58,7 +64,7 @@ impl<T: Send + 'static, V: View<T> + 'static> AppLauncher<T, V> {
|
|||
QUIT_MENU_ID,
|
||||
"E&xit",
|
||||
Some(&HotKey::new(SysMods::Cmd, "q")),
|
||||
true,
|
||||
Some(true),
|
||||
false,
|
||||
);
|
||||
let mut menubar = Menu::new();
|
||||
|
@ -71,6 +77,7 @@ impl<T: Send + 'static, V: View<T> + 'static> AppLauncher<T, V> {
|
|||
builder.set_handler(Box::new(main_state));
|
||||
builder.set_title(self.title);
|
||||
builder.set_menu(menubar);
|
||||
builder.set_size(Size::new(1024., 768.));
|
||||
let window = builder.build().unwrap();
|
||||
window.show();
|
||||
druid_app.run(None);
|
||||
|
@ -88,8 +95,17 @@ where
|
|||
|
||||
fn prepare_paint(&mut self) {}
|
||||
|
||||
fn paint(&mut self, piet: &mut druid_shell::piet::Piet, _: &Region) {
|
||||
self.app.paint(piet);
|
||||
fn paint(&mut self, _: &Region) {
|
||||
let rendered = self.app.paint();
|
||||
self.render(rendered.0);
|
||||
self.schedule_render();
|
||||
}
|
||||
|
||||
// TODO: temporary hack
|
||||
fn idle(&mut self, _: IdleToken) {
|
||||
let rendered = self.app.paint();
|
||||
self.render(rendered.0);
|
||||
self.schedule_render();
|
||||
}
|
||||
|
||||
fn command(&mut self, id: u32) {
|
||||
|
@ -135,11 +151,6 @@ where
|
|||
Application::global().quit()
|
||||
}
|
||||
|
||||
fn idle(&mut self, _token: IdleToken) {
|
||||
// TODO: wire up invalidation through widget hierarchy
|
||||
self.handle.invalidate();
|
||||
}
|
||||
|
||||
fn as_any(&mut self) -> &mut dyn Any {
|
||||
self
|
||||
}
|
||||
|
@ -153,7 +164,53 @@ where
|
|||
let state = MainState {
|
||||
handle: Default::default(),
|
||||
app,
|
||||
font_context: FontContext::new(),
|
||||
pgpu_state: None,
|
||||
scene: Scene::default(),
|
||||
counter: 0,
|
||||
};
|
||||
state
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
fn schedule_render(&self) {
|
||||
self.handle
|
||||
.get_idle_handle()
|
||||
.unwrap()
|
||||
.schedule_idle(IdleToken::new(0));
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
fn schedule_render(&self) {
|
||||
self.handle.invalidate();
|
||||
}
|
||||
|
||||
fn render(&mut self, fragment: SceneFragment) {
|
||||
if self.pgpu_state.is_none() {
|
||||
let handle = &self.handle;
|
||||
let scale = handle.get_scale().unwrap();
|
||||
let insets = handle.content_insets().to_px(scale);
|
||||
let mut size = handle.get_size().to_px(scale);
|
||||
size.width -= insets.x_value();
|
||||
size.height -= insets.y_value();
|
||||
println!("render size: {:?}", size);
|
||||
self.pgpu_state = Some(
|
||||
crate::render::PgpuState::new(
|
||||
handle,
|
||||
handle,
|
||||
size.width as usize,
|
||||
size.height as usize,
|
||||
)
|
||||
.unwrap(),
|
||||
);
|
||||
}
|
||||
if let Some(pgpu_state) = self.pgpu_state.as_mut() {
|
||||
if let Some(_timestamps) = pgpu_state.pre_render() {}
|
||||
let mut builder = SceneBuilder::for_scene(&mut self.scene);
|
||||
builder.append(&fragment, None);
|
||||
//crate::test_scenes::render(&mut self.font_context, &mut self.scene, 0, self.counter);
|
||||
self.counter += 1;
|
||||
pgpu_state.render(&self.scene);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,7 +23,7 @@ pub type IdPath = Vec<Id>;
|
|||
impl Id {
|
||||
/// Allocate a new, unique `Id`.
|
||||
pub fn next() -> Id {
|
||||
use druid_shell::Counter;
|
||||
use glazier::Counter;
|
||||
static WIDGET_ID_COUNTER: Counter = Counter::new();
|
||||
Id(WIDGET_ID_COUNTER.next_nonzero())
|
||||
}
|
||||
|
|
179
src/lib.rs
179
src/lib.rs
|
@ -1,40 +1,163 @@
|
|||
// Copyright 2022 The Druid Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Prototype implementation of Xilem architecture.
|
||||
//!
|
||||
//! This is a skeletal, proof-of-concept UI toolkit to prove out the Xilem
|
||||
//! architectural ideas.
|
||||
|
||||
mod app;
|
||||
mod app_main;
|
||||
mod event;
|
||||
mod id;
|
||||
mod render;
|
||||
mod test_scenes;
|
||||
mod text;
|
||||
mod view;
|
||||
mod view_seq;
|
||||
mod widget;
|
||||
|
||||
pub use app::App;
|
||||
pub use app_main::AppLauncher;
|
||||
pub use view::adapt::Adapt;
|
||||
pub use view::async_list::async_list;
|
||||
pub use view::button::button;
|
||||
pub use view::layout_observer::LayoutObserver;
|
||||
pub use view::list::list;
|
||||
pub use view::memoize::Memoize;
|
||||
pub use view::scroll_view::scroll_view;
|
||||
pub use view::vstack::v_stack;
|
||||
pub use view::View;
|
||||
pub use widget::align::{AlignmentAxis, AlignmentProxy, HorizAlignment, VertAlignment};
|
||||
pub use widget::align::VertAlignment;
|
||||
pub use widget::Widget;
|
||||
|
||||
use glazier::kurbo::Size;
|
||||
use glazier::{
|
||||
Application, Cursor, FileDialogToken, FileInfo, IdleToken, KeyEvent, MouseEvent, Region,
|
||||
Scalable, TimerToken, WinHandler, WindowHandle,
|
||||
};
|
||||
use parley::FontContext;
|
||||
use piet_scene::Scene;
|
||||
use std::any::Any;
|
||||
|
||||
pub struct WindowState {
|
||||
handle: WindowHandle,
|
||||
pgpu_state: Option<render::PgpuState>,
|
||||
scene: Scene,
|
||||
font_context: FontContext,
|
||||
counter: u64,
|
||||
}
|
||||
|
||||
impl WindowState {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
handle: Default::default(),
|
||||
pgpu_state: None,
|
||||
scene: Default::default(),
|
||||
font_context: FontContext::new(),
|
||||
counter: 0,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
fn schedule_render(&self) {
|
||||
self.handle
|
||||
.get_idle_handle()
|
||||
.unwrap()
|
||||
.schedule_idle(IdleToken::new(0));
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
fn schedule_render(&self) {
|
||||
self.handle.invalidate();
|
||||
}
|
||||
|
||||
fn render(&mut self) {
|
||||
if self.pgpu_state.is_none() {
|
||||
let handle = &self.handle;
|
||||
let scale = handle.get_scale().unwrap();
|
||||
let insets = handle.content_insets().to_px(scale);
|
||||
let mut size = handle.get_size().to_px(scale);
|
||||
size.width -= insets.x_value();
|
||||
size.height -= insets.y_value();
|
||||
println!("render size: {:?}", size);
|
||||
self.pgpu_state = Some(
|
||||
render::PgpuState::new(handle, handle, size.width as usize, size.height as usize)
|
||||
.unwrap(),
|
||||
);
|
||||
}
|
||||
if let Some(pgpu_state) = self.pgpu_state.as_mut() {
|
||||
if let Some(_timestamps) = pgpu_state.pre_render() {}
|
||||
test_scenes::render(&mut self.font_context, &mut self.scene, 0, self.counter);
|
||||
self.counter += 1;
|
||||
pgpu_state.render(&self.scene);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl WinHandler for WindowState {
|
||||
fn connect(&mut self, handle: &WindowHandle) {
|
||||
self.handle = handle.clone();
|
||||
self.schedule_render();
|
||||
}
|
||||
|
||||
fn prepare_paint(&mut self) {}
|
||||
|
||||
fn paint(&mut self, _: &Region) {
|
||||
self.render();
|
||||
self.schedule_render();
|
||||
}
|
||||
|
||||
fn idle(&mut self, _: IdleToken) {
|
||||
self.render();
|
||||
self.schedule_render();
|
||||
}
|
||||
|
||||
fn command(&mut self, _id: u32) {}
|
||||
|
||||
fn open_file(&mut self, _token: FileDialogToken, file_info: Option<FileInfo>) {
|
||||
println!("open file result: {:?}", file_info);
|
||||
}
|
||||
|
||||
fn save_as(&mut self, _token: FileDialogToken, file: Option<FileInfo>) {
|
||||
println!("save file result: {:?}", file);
|
||||
}
|
||||
|
||||
fn key_down(&mut self, event: KeyEvent) -> bool {
|
||||
println!("keydown: {:?}", event);
|
||||
false
|
||||
}
|
||||
|
||||
fn key_up(&mut self, event: KeyEvent) {
|
||||
println!("keyup: {:?}", event);
|
||||
}
|
||||
|
||||
fn wheel(&mut self, event: &MouseEvent) {
|
||||
println!("mouse_wheel {:?}", event);
|
||||
}
|
||||
|
||||
fn mouse_move(&mut self, _event: &MouseEvent) {
|
||||
self.handle.set_cursor(&Cursor::Arrow);
|
||||
//println!("mouse_move {:?}", event);
|
||||
}
|
||||
|
||||
fn mouse_down(&mut self, event: &MouseEvent) {
|
||||
println!("mouse_down {:?}", event);
|
||||
}
|
||||
|
||||
fn mouse_up(&mut self, event: &MouseEvent) {
|
||||
println!("mouse_up {:?}", event);
|
||||
}
|
||||
|
||||
fn timer(&mut self, id: TimerToken) {
|
||||
println!("timer fired: {:?}", id);
|
||||
}
|
||||
|
||||
fn size(&mut self, _size: Size) {
|
||||
//self.size = size;
|
||||
}
|
||||
|
||||
fn got_focus(&mut self) {
|
||||
println!("Got focus");
|
||||
}
|
||||
|
||||
fn lost_focus(&mut self) {
|
||||
println!("Lost focus");
|
||||
}
|
||||
|
||||
fn request_close(&mut self) {
|
||||
self.handle.close();
|
||||
}
|
||||
|
||||
fn destroy(&mut self) {
|
||||
Application::global().quit()
|
||||
}
|
||||
|
||||
fn as_any(&mut self) -> &mut dyn Any {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
use xilem::{button, App, AppLauncher, View};
|
||||
|
||||
fn app_logic(_data: &mut ()) -> impl View<()> {
|
||||
button("click me", |_| println!("clicked"))
|
||||
}
|
||||
|
||||
fn main() {
|
||||
/*
|
||||
let app = Application::new().unwrap();
|
||||
let mut window_builder = glazier::WindowBuilder::new(app.clone());
|
||||
window_builder.resizable(false);
|
||||
window_builder.set_size((WIDTH as f64 / 2., HEIGHT as f64 / 2.).into());
|
||||
window_builder.set_handler(Box::new(xilem::WindowState::new()));
|
||||
let window_handle = window_builder.build().unwrap();
|
||||
window_handle.show();
|
||||
app.run(None);
|
||||
*/
|
||||
let app = App::new((), app_logic);
|
||||
AppLauncher::new(app).run()
|
||||
}
|
|
@ -0,0 +1,123 @@
|
|||
use piet_gpu::Renderer;
|
||||
use piet_gpu_hal::{
|
||||
CmdBuf, Error, ImageLayout, Instance, QueryPool, Semaphore, Session, SubmittedCmdBuf, Surface,
|
||||
Swapchain,
|
||||
};
|
||||
use piet_scene::Scene;
|
||||
|
||||
pub const NUM_FRAMES: usize = 2;
|
||||
|
||||
pub struct PgpuState {
|
||||
#[allow(unused)]
|
||||
instance: Instance,
|
||||
#[allow(unused)]
|
||||
surface: Option<Surface>,
|
||||
swapchain: Swapchain,
|
||||
session: Session,
|
||||
present_semaphores: Vec<Semaphore>,
|
||||
query_pools: Vec<QueryPool>,
|
||||
cmd_bufs: [Option<CmdBuf>; NUM_FRAMES],
|
||||
submitted: [Option<SubmittedCmdBuf>; NUM_FRAMES],
|
||||
renderer: Renderer,
|
||||
current_frame: usize,
|
||||
}
|
||||
|
||||
impl PgpuState {
|
||||
pub fn new(
|
||||
window: &dyn raw_window_handle::HasRawWindowHandle,
|
||||
display: &dyn raw_window_handle::HasRawDisplayHandle,
|
||||
width: usize,
|
||||
height: usize,
|
||||
) -> Result<Self, Error> {
|
||||
println!("size: {}, {}", width, height);
|
||||
let instance = Instance::new(Default::default())?;
|
||||
let surface = unsafe {
|
||||
instance
|
||||
.surface(display.raw_display_handle(), window.raw_window_handle())
|
||||
.ok()
|
||||
};
|
||||
unsafe {
|
||||
let device = instance.device()?;
|
||||
let swapchain =
|
||||
instance.swapchain(width, height, &device, surface.as_ref().unwrap())?;
|
||||
let session = Session::new(device);
|
||||
let present_semaphores = (0..NUM_FRAMES)
|
||||
.map(|_| session.create_semaphore())
|
||||
.collect::<Result<Vec<_>, Error>>()?;
|
||||
let query_pools = (0..NUM_FRAMES)
|
||||
.map(|_| session.create_query_pool(Renderer::QUERY_POOL_SIZE))
|
||||
.collect::<Result<Vec<_>, Error>>()?;
|
||||
let cmd_bufs: [Option<CmdBuf>; NUM_FRAMES] = Default::default();
|
||||
let submitted: [Option<SubmittedCmdBuf>; NUM_FRAMES] = Default::default();
|
||||
let renderer = Renderer::new(&session, width, height, NUM_FRAMES)?;
|
||||
let current_frame = 0;
|
||||
Ok(Self {
|
||||
instance,
|
||||
surface,
|
||||
swapchain,
|
||||
session,
|
||||
present_semaphores,
|
||||
query_pools,
|
||||
cmd_bufs,
|
||||
submitted,
|
||||
renderer,
|
||||
current_frame,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn frame_index(&self) -> usize {
|
||||
self.current_frame % NUM_FRAMES
|
||||
}
|
||||
|
||||
pub fn pre_render(&mut self) -> Option<Vec<f64>> {
|
||||
let frame_idx = self.frame_index();
|
||||
if let Some(submitted) = self.submitted[frame_idx].take() {
|
||||
self.cmd_bufs[frame_idx] = submitted.wait().unwrap();
|
||||
Some(unsafe {
|
||||
self.session
|
||||
.fetch_query_pool(&self.query_pools[frame_idx])
|
||||
.unwrap()
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn render(&mut self, scene: &Scene) {
|
||||
let frame_idx = self.frame_index();
|
||||
self.renderer.upload_scene(&scene, frame_idx).unwrap();
|
||||
unsafe {
|
||||
let (image_idx, acquisition_semaphore) = self.swapchain.next().unwrap();
|
||||
let swap_image = self.swapchain.image(image_idx);
|
||||
let query_pool = &self.query_pools[frame_idx];
|
||||
let mut cmd_buf = self.cmd_bufs[frame_idx]
|
||||
.take()
|
||||
.unwrap_or_else(|| self.session.cmd_buf().unwrap());
|
||||
cmd_buf.begin();
|
||||
self.renderer.record(&mut cmd_buf, &query_pool, frame_idx);
|
||||
|
||||
// Image -> Swapchain
|
||||
cmd_buf.image_barrier(&swap_image, ImageLayout::Undefined, ImageLayout::BlitDst);
|
||||
cmd_buf.blit_image(&self.renderer.image_dev, &swap_image);
|
||||
cmd_buf.image_barrier(&swap_image, ImageLayout::BlitDst, ImageLayout::Present);
|
||||
cmd_buf.finish();
|
||||
|
||||
self.submitted[frame_idx] = Some(
|
||||
self.session
|
||||
.run_cmd_buf(
|
||||
cmd_buf,
|
||||
&[&acquisition_semaphore],
|
||||
&[&self.present_semaphores[frame_idx]],
|
||||
)
|
||||
.unwrap(),
|
||||
);
|
||||
|
||||
self.swapchain
|
||||
.present(image_idx, &[&self.present_semaphores[frame_idx]])
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
self.current_frame += 1;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
use super::text::*;
|
||||
use parley::FontContext;
|
||||
use piet_scene::*;
|
||||
|
||||
pub fn render(fcx: &mut FontContext, scene: &mut Scene, which: usize, arg: u64) {
|
||||
match which {
|
||||
_ => basic_scene(fcx, scene, arg),
|
||||
}
|
||||
}
|
||||
|
||||
fn basic_scene(fcx: &mut FontContext, scene: &mut Scene, arg: u64) {
|
||||
let transform = Affine::translate(400.0, 400.0) * Affine::rotate((arg as f64 * 0.01) as f32);
|
||||
let mut builder = SceneBuilder::for_scene(scene);
|
||||
let stops = &[
|
||||
GradientStop {
|
||||
offset: 0.0,
|
||||
color: Color::rgb8(128, 0, 0),
|
||||
},
|
||||
GradientStop {
|
||||
offset: 0.5,
|
||||
color: Color::rgb8(0, 128, 0),
|
||||
},
|
||||
GradientStop {
|
||||
offset: 1.0,
|
||||
color: Color::rgb8(0, 0, 128),
|
||||
},
|
||||
][..];
|
||||
let gradient = Brush::LinearGradient(LinearGradient {
|
||||
start: Point::new(0.0, 0.0),
|
||||
end: Point::new(0.0, 400.0),
|
||||
extend: ExtendMode::Pad,
|
||||
stops: stops.iter().copied().collect(),
|
||||
});
|
||||
builder.fill(
|
||||
Fill::NonZero,
|
||||
transform,
|
||||
&gradient,
|
||||
None,
|
||||
Rect {
|
||||
min: Point::new(0.0, 0.0),
|
||||
max: Point::new(600.0, 400.0),
|
||||
}
|
||||
.elements(),
|
||||
);
|
||||
let scale = (arg as f64 * 0.01).sin() * 0.5 + 1.5;
|
||||
let mut lcx = parley::LayoutContext::new();
|
||||
let mut layout_builder =
|
||||
lcx.ranged_builder(fcx, "Hello piet-gpu! ഹലോ ਸਤ ਸ੍ਰੀ ਅਕਾਲ مرحبا!", scale as f32);
|
||||
layout_builder.push_default(&parley::style::StyleProperty::FontSize(34.0));
|
||||
layout_builder.push(
|
||||
&parley::style::StyleProperty::Brush(ParleyBrush(Brush::Solid(Color::rgb8(255, 255, 0)))),
|
||||
6..10,
|
||||
);
|
||||
layout_builder.push(&parley::style::StyleProperty::FontSize(48.0), 6..10);
|
||||
layout_builder.push_default(&parley::style::StyleProperty::Brush(ParleyBrush(
|
||||
Brush::Solid(Color::rgb8(255, 255, 255)),
|
||||
)));
|
||||
let mut layout = layout_builder.build();
|
||||
layout.break_all_lines(None, parley::layout::Alignment::Start);
|
||||
render_text(&mut builder, Affine::translate(100.0, 400.0), &layout);
|
||||
builder.finish();
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
use parley::Layout;
|
||||
use piet_scene::{
|
||||
glyph::{
|
||||
pinot::{types::Tag, FontRef},
|
||||
GlyphContext,
|
||||
},
|
||||
*,
|
||||
};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ParleyBrush(pub Brush);
|
||||
|
||||
impl Default for ParleyBrush {
|
||||
fn default() -> ParleyBrush {
|
||||
ParleyBrush(Brush::Solid(Color::rgb8(0, 0, 0)))
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<ParleyBrush> for ParleyBrush {
|
||||
fn eq(&self, _other: &ParleyBrush) -> bool {
|
||||
true // FIXME
|
||||
}
|
||||
}
|
||||
|
||||
impl parley::style::Brush for ParleyBrush {}
|
||||
|
||||
pub fn render_text(builder: &mut SceneBuilder, transform: Affine, layout: &Layout<ParleyBrush>) {
|
||||
let mut gcx = GlyphContext::new();
|
||||
for line in layout.lines() {
|
||||
for glyph_run in line.glyph_runs() {
|
||||
let mut x = glyph_run.offset();
|
||||
let y = glyph_run.baseline();
|
||||
let run = glyph_run.run();
|
||||
let font = run.font().as_ref();
|
||||
let font_size = run.font_size();
|
||||
let font_ref = FontRef {
|
||||
data: font.data,
|
||||
offset: font.offset,
|
||||
};
|
||||
let style = glyph_run.style();
|
||||
let vars: [(Tag, f32); 0] = [];
|
||||
let mut gp = gcx.new_provider(&font_ref, None, font_size, false, vars);
|
||||
for glyph in glyph_run.glyphs() {
|
||||
if let Some(fragment) = gp.get(glyph.id, Some(&style.brush.0)) {
|
||||
let gx = x + glyph.x;
|
||||
let gy = y - glyph.y;
|
||||
let xform = Affine::translate(gx, gy) * Affine::scale(1.0, -1.0);
|
||||
builder.append(&fragment, Some(transform * xform));
|
||||
}
|
||||
x += glyph.advance;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
18
src/view.rs
18
src/view.rs
|
@ -12,17 +12,17 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
pub mod adapt;
|
||||
pub mod any_view;
|
||||
pub mod async_list;
|
||||
// pub mod adapt;
|
||||
// pub mod any_view;
|
||||
// pub mod async_list;
|
||||
pub mod button;
|
||||
pub mod layout_observer;
|
||||
pub mod list;
|
||||
pub mod memoize;
|
||||
pub mod scroll_view;
|
||||
// pub mod layout_observer;
|
||||
// pub mod list;
|
||||
// pub mod memoize;
|
||||
// pub mod scroll_view;
|
||||
pub mod text;
|
||||
pub mod use_state;
|
||||
pub mod vstack;
|
||||
// pub mod use_state;
|
||||
// pub mod vstack;
|
||||
|
||||
use std::{
|
||||
any::Any,
|
||||
|
|
122
src/view_seq.rs
122
src/view_seq.rs
|
@ -1,122 +0,0 @@
|
|||
// Copyright 2022 The Druid Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use std::any::Any;
|
||||
|
||||
use crate::{
|
||||
event::EventResult,
|
||||
id::Id,
|
||||
view::{Cx, View},
|
||||
widget::Pod,
|
||||
};
|
||||
|
||||
pub trait ViewSequence<T, A>: Send {
|
||||
type State: Send;
|
||||
|
||||
type Elements;
|
||||
|
||||
fn build(&self, cx: &mut Cx) -> (Self::State, Vec<Pod>);
|
||||
|
||||
fn rebuild(
|
||||
&self,
|
||||
cx: &mut Cx,
|
||||
prev: &Self,
|
||||
state: &mut Self::State,
|
||||
els: &mut Vec<Pod>,
|
||||
) -> bool;
|
||||
|
||||
fn event(
|
||||
&self,
|
||||
id_path: &[Id],
|
||||
state: &mut Self::State,
|
||||
event: Box<dyn Any>,
|
||||
app_state: &mut T,
|
||||
) -> EventResult<A>;
|
||||
}
|
||||
|
||||
macro_rules! impl_view_tuple {
|
||||
( $n: tt; $( $t:ident),* ; $( $i:tt ),* ) => {
|
||||
impl<T, A, $( $t: View<T, A> ),* > ViewSequence<T, A> for ( $( $t, )* )
|
||||
where $( <$t as View<T, A>>::Element: 'static ),*
|
||||
{
|
||||
type State = ( $( $t::State, )* [Id; $n]);
|
||||
|
||||
type Elements = ( $( $t::Element, )* );
|
||||
|
||||
fn build(&self, cx: &mut Cx) -> (Self::State, Vec<Pod>) {
|
||||
let b = ( $( self.$i.build(cx), )* );
|
||||
let state = ( $( b.$i.1, )* [ $( b.$i.0 ),* ]);
|
||||
let els = vec![ $( Pod::new(b.$i.2) ),* ];
|
||||
(state, els)
|
||||
}
|
||||
|
||||
fn rebuild(
|
||||
&self,
|
||||
cx: &mut Cx,
|
||||
prev: &Self,
|
||||
state: &mut Self::State,
|
||||
els: &mut Vec<Pod>,
|
||||
) -> bool {
|
||||
let mut changed = false;
|
||||
$(
|
||||
if self.$i
|
||||
.rebuild(cx, &prev.$i, &mut state.$n[$i], &mut state.$i,
|
||||
els[$i].downcast_mut().unwrap())
|
||||
{
|
||||
els[$i].request_update();
|
||||
changed = true;
|
||||
}
|
||||
)*
|
||||
changed
|
||||
}
|
||||
|
||||
fn event(
|
||||
&self,
|
||||
id_path: &[Id],
|
||||
state: &mut Self::State,
|
||||
event: Box<dyn Any>,
|
||||
app_state: &mut T,
|
||||
) -> EventResult<A> {
|
||||
let hd = id_path[0];
|
||||
let tl = &id_path[1..];
|
||||
$(
|
||||
if hd == state.$n[$i] {
|
||||
self.$i.event(tl, &mut state.$i, event, app_state)
|
||||
} else )* {
|
||||
crate::event::EventResult::Stale
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl_view_tuple!(1; V0; 0);
|
||||
impl_view_tuple!(2; V0, V1; 0, 1);
|
||||
impl_view_tuple!(3; V0, V1, V2; 0, 1, 2);
|
||||
impl_view_tuple!(4; V0, V1, V2, V3; 0, 1, 2, 3);
|
||||
impl_view_tuple!(5; V0, V1, V2, V3, V4; 0, 1, 2, 3, 4);
|
||||
impl_view_tuple!(6; V0, V1, V2, V3, V4, V5; 0, 1, 2, 3, 4, 5);
|
||||
impl_view_tuple!(7; V0, V1, V2, V3, V4, V5, V6; 0, 1, 2, 3, 4, 5, 6);
|
||||
impl_view_tuple!(8;
|
||||
V0, V1, V2, V3, V4, V5, V6, V7;
|
||||
0, 1, 2, 3, 4, 5, 6, 7
|
||||
);
|
||||
impl_view_tuple!(9;
|
||||
V0, V1, V2, V3, V4, V5, V6, V7, V8;
|
||||
0, 1, 2, 3, 4, 5, 6, 7, 8
|
||||
);
|
||||
impl_view_tuple!(10;
|
||||
V0, V1, V2, V3, V4, V5, V6, V7, V8, V9;
|
||||
0, 1, 2, 3, 4, 5, 6, 7, 8, 9
|
||||
);
|
|
@ -16,17 +16,18 @@ pub mod align;
|
|||
pub mod button;
|
||||
mod contexts;
|
||||
mod core;
|
||||
pub mod layout_observer;
|
||||
pub mod list;
|
||||
//pub mod layout_observer;
|
||||
//pub mod list;
|
||||
pub mod piet_scene_helpers;
|
||||
mod raw_event;
|
||||
pub mod scroll_view;
|
||||
//pub mod scroll_view;
|
||||
pub mod text;
|
||||
pub mod vstack;
|
||||
//pub mod vstack;
|
||||
|
||||
use std::any::Any;
|
||||
use std::ops::{Deref, DerefMut};
|
||||
|
||||
use druid_shell::kurbo::{Rect, Size};
|
||||
use glazier::kurbo::{Rect, Size};
|
||||
|
||||
use self::contexts::LifeCycleCx;
|
||||
pub use self::contexts::{AlignCx, CxState, EventCx, LayoutCx, PaintCx, PreparePaintCx, UpdateCx};
|
||||
|
@ -78,9 +79,11 @@ pub trait Widget {
|
|||
#[allow(unused)]
|
||||
fn prepare_paint(&mut self, cx: &mut LayoutCx, visible: Rect) {}
|
||||
|
||||
fn paint(&mut self, cx: &mut PaintCx);
|
||||
fn paint(&mut self, cx: &mut PaintCx) -> Rendered;
|
||||
}
|
||||
|
||||
pub struct Rendered(pub(crate) piet_scene::SceneFragment);
|
||||
|
||||
pub trait AnyWidget: Widget {
|
||||
fn as_any(&self) -> &dyn Any;
|
||||
|
||||
|
@ -132,8 +135,8 @@ impl Widget for Box<dyn AnyWidget> {
|
|||
self.deref_mut().prepare_paint(cx, visible)
|
||||
}
|
||||
|
||||
fn paint(&mut self, cx: &mut PaintCx) {
|
||||
self.deref_mut().paint(cx);
|
||||
fn paint(&mut self, cx: &mut PaintCx) -> Rendered {
|
||||
self.deref_mut().paint(cx)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -12,9 +12,11 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use druid_shell::kurbo::Point;
|
||||
use glazier::kurbo::{Point, Size};
|
||||
|
||||
use super::{contexts::LifeCycleCx, AlignCx, AnyWidget, EventCx, LifeCycle, Widget, WidgetState};
|
||||
use super::{
|
||||
contexts::LifeCycleCx, AlignCx, AnyWidget, EventCx, LifeCycle, Rendered, Widget, WidgetState,
|
||||
};
|
||||
|
||||
#[derive(Clone, Copy, PartialEq)]
|
||||
pub enum AlignmentMerge {
|
||||
|
@ -253,18 +255,11 @@ impl<F: Fn(AlignmentProxy) -> f64 + 'static> Widget for AlignmentGuide<F> {
|
|||
self.child.update(cx);
|
||||
}
|
||||
|
||||
fn measure(
|
||||
&mut self,
|
||||
cx: &mut super::LayoutCx,
|
||||
) -> (druid_shell::kurbo::Size, druid_shell::kurbo::Size) {
|
||||
fn measure(&mut self, cx: &mut super::LayoutCx) -> (Size, Size) {
|
||||
self.child.measure(cx)
|
||||
}
|
||||
|
||||
fn layout(
|
||||
&mut self,
|
||||
cx: &mut super::LayoutCx,
|
||||
proposed_size: druid_shell::kurbo::Size,
|
||||
) -> druid_shell::kurbo::Size {
|
||||
fn layout(&mut self, cx: &mut super::LayoutCx, proposed_size: Size) -> Size {
|
||||
self.child.layout(cx, proposed_size)
|
||||
}
|
||||
|
||||
|
@ -281,7 +276,7 @@ impl<F: Fn(AlignmentProxy) -> f64 + 'static> Widget for AlignmentGuide<F> {
|
|||
}
|
||||
}
|
||||
|
||||
fn paint(&mut self, cx: &mut super::PaintCx) {
|
||||
self.child.paint(cx);
|
||||
fn paint(&mut self, cx: &mut super::PaintCx) -> Rendered {
|
||||
self.child.paint(cx)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,26 +12,23 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use druid_shell::{
|
||||
kurbo::{Insets, Size},
|
||||
piet::{
|
||||
Color, LinearGradient, PietTextLayout, RenderContext, Text, TextLayout, TextLayoutBuilder,
|
||||
UnitPoint,
|
||||
},
|
||||
};
|
||||
use glazier::kurbo::{Insets, Size};
|
||||
use parley::Layout;
|
||||
use piet_scene::{Affine, Brush, Color, GradientStop, GradientStops, SceneBuilder, SceneFragment};
|
||||
|
||||
use crate::{event::Event, id::IdPath, VertAlignment};
|
||||
use crate::{event::Event, id::IdPath, text::ParleyBrush, VertAlignment};
|
||||
|
||||
use super::{
|
||||
align::{FirstBaseline, LastBaseline, SingleAlignment},
|
||||
contexts::LifeCycleCx,
|
||||
AlignCx, EventCx, LayoutCx, LifeCycle, PaintCx, RawEvent, UpdateCx, Widget,
|
||||
piet_scene_helpers::{self, UnitPoint},
|
||||
AlignCx, EventCx, LayoutCx, LifeCycle, PaintCx, RawEvent, Rendered, UpdateCx, Widget,
|
||||
};
|
||||
|
||||
pub struct Button {
|
||||
id_path: IdPath,
|
||||
label: String,
|
||||
layout: Option<PietTextLayout>,
|
||||
layout: Option<Layout<ParleyBrush>>,
|
||||
}
|
||||
|
||||
impl Button {
|
||||
|
@ -84,15 +81,18 @@ impl Widget for Button {
|
|||
fn measure(&mut self, cx: &mut LayoutCx) -> (Size, Size) {
|
||||
let padding = Size::new(LABEL_INSETS.x_value(), LABEL_INSETS.y_value());
|
||||
let min_height = 24.0;
|
||||
let layout = cx
|
||||
.text()
|
||||
.new_text_layout(self.label.clone())
|
||||
.text_color(Color::rgb8(0xf0, 0xf0, 0xea))
|
||||
.build()
|
||||
.unwrap();
|
||||
let mut lcx = parley::LayoutContext::new();
|
||||
let mut layout_builder = lcx.ranged_builder(cx.font_cx(), &self.label, 1.0);
|
||||
|
||||
layout_builder.push_default(&parley::style::StyleProperty::Brush(ParleyBrush(
|
||||
Brush::Solid(Color::rgb8(0xf0, 0xf0, 0xea)),
|
||||
)));
|
||||
let mut layout = layout_builder.build();
|
||||
// Question for Chad: is this needed?
|
||||
layout.break_all_lines(None, parley::layout::Alignment::Start);
|
||||
let size = Size::new(
|
||||
layout.size().width + padding.width,
|
||||
(layout.size().height + padding.height).max(min_height),
|
||||
layout.width() as f64 + padding.width,
|
||||
(layout.height() as f64 + padding.height).max(min_height),
|
||||
);
|
||||
self.layout = Some(layout);
|
||||
(Size::new(10.0, min_height), size)
|
||||
|
@ -105,10 +105,13 @@ impl Widget for Button {
|
|||
.clamp(cx.min_size().width, cx.max_size().width),
|
||||
cx.max_size().height,
|
||||
);
|
||||
println!("size = {:?}", size);
|
||||
size
|
||||
}
|
||||
|
||||
fn align(&self, cx: &mut AlignCx, alignment: SingleAlignment) {
|
||||
// TODO: figure this out
|
||||
/*
|
||||
if alignment.id() == FirstBaseline.id() || alignment.id() == LastBaseline.id() {
|
||||
let layout = self.layout.as_ref().unwrap();
|
||||
if let Some(metric) = layout.line_metric(0) {
|
||||
|
@ -116,9 +119,10 @@ impl Widget for Button {
|
|||
cx.aggregate(alignment, value);
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
fn paint(&mut self, cx: &mut PaintCx) {
|
||||
fn paint(&mut self, cx: &mut PaintCx) -> Rendered {
|
||||
let is_hot = cx.is_hot();
|
||||
let is_active = cx.is_active();
|
||||
let button_border_width = 2.0;
|
||||
|
@ -132,6 +136,32 @@ impl Widget for Button {
|
|||
} else {
|
||||
Color::rgb8(0x3a, 0x3a, 0x3a)
|
||||
};
|
||||
let bg_stops = if is_active {
|
||||
[
|
||||
GradientStop {
|
||||
offset: 0.0,
|
||||
color: Color::rgb8(0x3a, 0x3a, 0x3a),
|
||||
},
|
||||
GradientStop {
|
||||
offset: 1.0,
|
||||
color: Color::rgb8(0xa1, 0xa1, 0xa1),
|
||||
},
|
||||
][..]
|
||||
.into()
|
||||
} else {
|
||||
[
|
||||
GradientStop {
|
||||
offset: 0.0,
|
||||
color: Color::rgb8(0xa1, 0xa1, 0xa1),
|
||||
},
|
||||
GradientStop {
|
||||
offset: 1.0,
|
||||
color: Color::rgb8(0x3a, 0x3a, 0x3a),
|
||||
},
|
||||
][..]
|
||||
.into()
|
||||
};
|
||||
/*
|
||||
let bg_gradient = if is_active {
|
||||
LinearGradient::new(
|
||||
UnitPoint::TOP,
|
||||
|
@ -145,10 +175,29 @@ impl Widget for Button {
|
|||
(Color::rgb8(0xa1, 0xa1, 0xa1), Color::rgb8(0x3a, 0x3a, 0x3a)),
|
||||
)
|
||||
};
|
||||
cx.stroke(rounded_rect, &border_color, button_border_width);
|
||||
cx.fill(rounded_rect, &bg_gradient);
|
||||
let layout = self.layout.as_ref().unwrap();
|
||||
let offset = (cx.size().to_vec2() - layout.size().to_vec2()) * 0.5;
|
||||
cx.draw_text(layout, offset.to_point());
|
||||
*/
|
||||
let mut fragment = SceneFragment::default();
|
||||
let mut builder = SceneBuilder::for_fragment(&mut fragment);
|
||||
piet_scene_helpers::stroke(
|
||||
&mut builder,
|
||||
&rounded_rect,
|
||||
&Brush::Solid(border_color),
|
||||
button_border_width,
|
||||
);
|
||||
piet_scene_helpers::fill_lin_gradient(
|
||||
&mut builder,
|
||||
&rounded_rect,
|
||||
bg_stops,
|
||||
UnitPoint::TOP,
|
||||
UnitPoint::BOTTOM,
|
||||
);
|
||||
//cx.fill(rounded_rect, &bg_gradient);
|
||||
if let Some(layout) = &self.layout {
|
||||
let size = Size::new(layout.width() as f64, layout.height() as f64);
|
||||
let offset = (cx.size().to_vec2() - size.to_vec2()) * 0.5;
|
||||
let transform = Affine::translate(offset.x as f32, offset.y as f32);
|
||||
crate::text::render_text(&mut builder, transform, &layout);
|
||||
}
|
||||
Rendered(fragment)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,11 +19,11 @@
|
|||
|
||||
use std::ops::{Deref, DerefMut};
|
||||
|
||||
use druid_shell::{
|
||||
use glazier::{
|
||||
kurbo::{Point, Size},
|
||||
piet::{Piet, PietText, RenderContext},
|
||||
WindowHandle,
|
||||
};
|
||||
use parley::FontContext;
|
||||
|
||||
use crate::event::Event;
|
||||
|
||||
|
@ -35,7 +35,7 @@ use super::{
|
|||
// These contexts loosely follow Druid.
|
||||
pub struct CxState<'a> {
|
||||
window: &'a WindowHandle,
|
||||
text: PietText,
|
||||
font_cx: &'a mut FontContext,
|
||||
events: &'a mut Vec<Event>,
|
||||
}
|
||||
|
||||
|
@ -66,17 +66,20 @@ pub struct AlignCx<'a> {
|
|||
pub(crate) origin: Point,
|
||||
}
|
||||
|
||||
pub struct PaintCx<'a, 'b, 'c> {
|
||||
pub struct PaintCx<'a, 'b> {
|
||||
pub(crate) cx_state: &'a mut CxState<'b>,
|
||||
pub(crate) widget_state: &'a WidgetState,
|
||||
pub(crate) piet: &'a mut Piet<'c>,
|
||||
}
|
||||
|
||||
impl<'a> CxState<'a> {
|
||||
pub fn new(window: &'a WindowHandle, events: &'a mut Vec<Event>) -> Self {
|
||||
pub fn new(
|
||||
window: &'a WindowHandle,
|
||||
font_cx: &'a mut FontContext,
|
||||
events: &'a mut Vec<Event>,
|
||||
) -> Self {
|
||||
CxState {
|
||||
window,
|
||||
text: window.text(),
|
||||
font_cx,
|
||||
events,
|
||||
}
|
||||
}
|
||||
|
@ -143,10 +146,6 @@ impl<'a, 'b> LayoutCx<'a, 'b> {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn text(&mut self) -> &mut PietText {
|
||||
&mut self.cx_state.text
|
||||
}
|
||||
|
||||
pub fn add_event(&mut self, event: Event) {
|
||||
self.cx_state.events.push(event);
|
||||
}
|
||||
|
@ -161,6 +160,10 @@ impl<'a, 'b> LayoutCx<'a, 'b> {
|
|||
pub fn max_size(&self) -> Size {
|
||||
self.widget_state.max_size
|
||||
}
|
||||
|
||||
pub fn font_cx(&mut self) -> &mut FontContext {
|
||||
self.cx_state.font_cx
|
||||
}
|
||||
}
|
||||
|
||||
// This is laziness, should be a separate cx with invalidate methods
|
||||
|
@ -180,25 +183,14 @@ impl<'a> AlignCx<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<'a, 'b, 'c> PaintCx<'a, 'b, 'c> {
|
||||
pub(crate) fn new(
|
||||
cx_state: &'a mut CxState<'b>,
|
||||
widget_state: &'a mut WidgetState,
|
||||
piet: &'a mut Piet<'c>,
|
||||
) -> Self {
|
||||
impl<'a, 'b> PaintCx<'a, 'b> {
|
||||
pub(crate) fn new(cx_state: &'a mut CxState<'b>, widget_state: &'a mut WidgetState) -> Self {
|
||||
PaintCx {
|
||||
cx_state,
|
||||
widget_state,
|
||||
piet,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_save(&mut self, f: impl FnOnce(&mut PaintCx)) {
|
||||
self.piet.save().unwrap();
|
||||
f(self);
|
||||
self.piet.restore().unwrap();
|
||||
}
|
||||
|
||||
pub fn is_hot(&self) -> bool {
|
||||
self.widget_state.flags.contains(PodFlags::IS_HOT)
|
||||
}
|
||||
|
@ -210,18 +202,8 @@ impl<'a, 'b, 'c> PaintCx<'a, 'b, 'c> {
|
|||
pub fn size(&self) -> Size {
|
||||
self.widget_state.size
|
||||
}
|
||||
}
|
||||
|
||||
impl<'c> Deref for PaintCx<'_, '_, 'c> {
|
||||
type Target = Piet<'c>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
self.piet
|
||||
}
|
||||
}
|
||||
|
||||
impl<'c> DerefMut for PaintCx<'_, '_, 'c> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
self.piet
|
||||
pub fn font_cx(&mut self) -> &mut FontContext {
|
||||
self.cx_state.font_cx
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,10 +18,7 @@
|
|||
//! widget system, particularly its core.rs.
|
||||
|
||||
use bitflags::bitflags;
|
||||
use druid_shell::{
|
||||
kurbo::{Affine, Point, Rect, Size},
|
||||
piet::RenderContext,
|
||||
};
|
||||
use glazier::kurbo::{Affine, Point, Rect, Size};
|
||||
|
||||
use crate::Widget;
|
||||
|
||||
|
@ -32,7 +29,7 @@ use super::{
|
|||
},
|
||||
contexts::LifeCycleCx,
|
||||
AlignCx, AnyWidget, CxState, EventCx, LayoutCx, LifeCycle, PaintCx, PreparePaintCx, RawEvent,
|
||||
UpdateCx,
|
||||
Rendered, UpdateCx,
|
||||
};
|
||||
|
||||
bitflags! {
|
||||
|
@ -277,6 +274,7 @@ impl Pod {
|
|||
widget_state: &mut self.state,
|
||||
};
|
||||
let new_size = self.widget.layout(&mut child_cx, proposed_size);
|
||||
println!("layout size = {:?}", new_size);
|
||||
self.state.proposed_size = proposed_size;
|
||||
self.state.size = new_size;
|
||||
self.state.flags.remove(PodFlags::REQUEST_LAYOUT);
|
||||
|
@ -301,7 +299,6 @@ impl Pod {
|
|||
let mut inner_cx = PaintCx {
|
||||
cx_state: cx.cx_state,
|
||||
widget_state: &mut self.state,
|
||||
piet: cx.piet,
|
||||
};
|
||||
self.widget.paint(&mut inner_cx);
|
||||
}
|
||||
|
@ -310,12 +307,12 @@ impl Pod {
|
|||
self.widget.prepare_paint(cx, visible);
|
||||
}
|
||||
|
||||
pub fn paint(&mut self, cx: &mut PaintCx) {
|
||||
cx.with_save(|cx| {
|
||||
cx.piet
|
||||
.transform(Affine::translate(self.state.origin.to_vec2()));
|
||||
self.paint_raw(cx);
|
||||
});
|
||||
pub fn paint(&mut self, cx: &mut PaintCx) -> Rendered {
|
||||
let mut inner_cx = PaintCx {
|
||||
cx_state: cx.cx_state,
|
||||
widget_state: &mut self.state,
|
||||
};
|
||||
self.widget.paint(&mut inner_cx)
|
||||
}
|
||||
|
||||
pub fn height_flexibility(&self) -> f64 {
|
||||
|
|
|
@ -0,0 +1,96 @@
|
|||
use glazier::kurbo::{self, Rect, Shape};
|
||||
use piet_scene::{
|
||||
Affine, Brush, Cap, ExtendMode, Fill, GradientStops, Join, LinearGradient, PathElement, Point,
|
||||
SceneBuilder, Stroke,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct UnitPoint {
|
||||
u: f64,
|
||||
v: f64,
|
||||
}
|
||||
|
||||
pub fn stroke(builder: &mut SceneBuilder, path: &impl Shape, brush: &Brush, stroke_width: f64) {
|
||||
let style = Stroke {
|
||||
width: stroke_width as f32,
|
||||
join: Join::Round,
|
||||
miter_limit: 1.0,
|
||||
start_cap: Cap::Round,
|
||||
end_cap: Cap::Round,
|
||||
dash_pattern: [],
|
||||
dash_offset: 0.0,
|
||||
scale: false,
|
||||
};
|
||||
// TODO: figure out how to avoid allocation
|
||||
// (Just removing the collect should work in theory, but running into a clone bound)
|
||||
let elements = path
|
||||
.path_elements(1e-3)
|
||||
.map(PathElement::from_kurbo)
|
||||
.collect::<Vec<_>>();
|
||||
builder.stroke(&style, Affine::IDENTITY, brush, None, &elements)
|
||||
}
|
||||
|
||||
// Note: copied from piet
|
||||
impl UnitPoint {
|
||||
/// `(0.0, 0.0)`
|
||||
pub const TOP_LEFT: UnitPoint = UnitPoint::new(0.0, 0.0);
|
||||
/// `(0.5, 0.0)`
|
||||
pub const TOP: UnitPoint = UnitPoint::new(0.5, 0.0);
|
||||
/// `(1.0, 0.0)`
|
||||
pub const TOP_RIGHT: UnitPoint = UnitPoint::new(1.0, 0.0);
|
||||
/// `(0.0, 0.5)`
|
||||
pub const LEFT: UnitPoint = UnitPoint::new(0.0, 0.5);
|
||||
/// `(0.5, 0.5)`
|
||||
pub const CENTER: UnitPoint = UnitPoint::new(0.5, 0.5);
|
||||
/// `(1.0, 0.5)`
|
||||
pub const RIGHT: UnitPoint = UnitPoint::new(1.0, 0.5);
|
||||
/// `(0.0, 1.0)`
|
||||
pub const BOTTOM_LEFT: UnitPoint = UnitPoint::new(0.0, 1.0);
|
||||
/// `(0.5, 1.0)`
|
||||
pub const BOTTOM: UnitPoint = UnitPoint::new(0.5, 1.0);
|
||||
/// `(1.0, 1.0)`
|
||||
pub const BOTTOM_RIGHT: UnitPoint = UnitPoint::new(1.0, 1.0);
|
||||
|
||||
/// Create a new UnitPoint.
|
||||
///
|
||||
/// The `u` and `v` coordinates describe the point, with (0.0, 0.0) being
|
||||
/// the top-left, and (1.0, 1.0) being the bottom-right.
|
||||
pub const fn new(u: f64, v: f64) -> UnitPoint {
|
||||
UnitPoint { u, v }
|
||||
}
|
||||
|
||||
/// Given a rectangle, resolve the point within the rectangle.
|
||||
pub fn resolve(self, rect: Rect) -> kurbo::Point {
|
||||
kurbo::Point::new(
|
||||
rect.x0 + self.u * (rect.x1 - rect.x0),
|
||||
rect.y0 + self.v * (rect.y1 - rect.y0),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn fill_lin_gradient(
|
||||
builder: &mut SceneBuilder,
|
||||
path: &impl Shape,
|
||||
stops: GradientStops,
|
||||
start: UnitPoint,
|
||||
end: UnitPoint,
|
||||
) {
|
||||
let rect = path.bounding_box();
|
||||
let lin_grad = LinearGradient {
|
||||
start: Point::from_kurbo(start.resolve(rect)),
|
||||
end: Point::from_kurbo(end.resolve(rect)),
|
||||
stops,
|
||||
extend: ExtendMode::Pad,
|
||||
};
|
||||
let elements = path
|
||||
.path_elements(1e-3)
|
||||
.map(PathElement::from_kurbo)
|
||||
.collect::<Vec<_>>();
|
||||
builder.fill(
|
||||
Fill::NonZero,
|
||||
Affine::IDENTITY,
|
||||
&Brush::LinearGradient(lin_grad),
|
||||
None,
|
||||
elements,
|
||||
);
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
use druid_shell::{
|
||||
use glazier::{
|
||||
kurbo::{Point, Vec2},
|
||||
Modifiers, MouseButton, MouseButtons,
|
||||
};
|
||||
|
@ -44,9 +44,9 @@ pub enum LifeCycle {
|
|||
HotChanged(bool),
|
||||
}
|
||||
|
||||
impl<'a> From<&'a druid_shell::MouseEvent> for MouseEvent {
|
||||
fn from(src: &druid_shell::MouseEvent) -> MouseEvent {
|
||||
let druid_shell::MouseEvent {
|
||||
impl<'a> From<&'a glazier::MouseEvent> for MouseEvent {
|
||||
fn from(src: &glazier::MouseEvent) -> MouseEvent {
|
||||
let glazier::MouseEvent {
|
||||
pos,
|
||||
buttons,
|
||||
mods,
|
||||
|
|
|
@ -12,21 +12,21 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use druid_shell::{
|
||||
kurbo::{Point, Size},
|
||||
piet::{Color, PietTextLayout, RenderContext, Text, TextLayout, TextLayoutBuilder},
|
||||
};
|
||||
use glazier::kurbo::{Point, Size};
|
||||
use parley::Layout;
|
||||
use piet_scene::{Affine, Brush, Color, SceneBuilder, SceneFragment};
|
||||
|
||||
use crate::text::ParleyBrush;
|
||||
|
||||
use super::{
|
||||
align::{FirstBaseline, LastBaseline, SingleAlignment, VertAlignment},
|
||||
contexts::LifeCycleCx,
|
||||
AlignCx, EventCx, LayoutCx, LifeCycle, PaintCx, RawEvent, UpdateCx, Widget,
|
||||
AlignCx, EventCx, LayoutCx, LifeCycle, PaintCx, RawEvent, Rendered, UpdateCx, Widget,
|
||||
};
|
||||
|
||||
pub struct TextWidget {
|
||||
text: String,
|
||||
color: Color,
|
||||
layout: Option<PietTextLayout>,
|
||||
layout: Option<Layout<ParleyBrush>>,
|
||||
is_wrapped: bool,
|
||||
}
|
||||
|
||||
|
@ -34,15 +34,13 @@ impl TextWidget {
|
|||
pub fn new(text: String) -> TextWidget {
|
||||
TextWidget {
|
||||
text,
|
||||
color: Color::WHITE,
|
||||
layout: None,
|
||||
is_wrapped: false,
|
||||
layout: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_text(&mut self, text: String) {
|
||||
self.text = text;
|
||||
self.layout = None;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -58,52 +56,34 @@ impl Widget for TextWidget {
|
|||
}
|
||||
|
||||
fn measure(&mut self, cx: &mut LayoutCx) -> (Size, Size) {
|
||||
let layout = cx
|
||||
.text()
|
||||
.new_text_layout(self.text.clone())
|
||||
.text_color(self.color.clone())
|
||||
.build()
|
||||
.unwrap();
|
||||
let min_size = Size::ZERO;
|
||||
let max_size = layout.size();
|
||||
self.layout = Some(layout);
|
||||
let max_size = Size::new(50.0, 50.0);
|
||||
self.is_wrapped = false;
|
||||
(min_size, max_size)
|
||||
}
|
||||
|
||||
fn layout(&mut self, cx: &mut LayoutCx, proposed_size: Size) -> Size {
|
||||
let needs_wrap = proposed_size.width < cx.widget_state.max_size.width;
|
||||
if self.is_wrapped || needs_wrap {
|
||||
let layout = cx
|
||||
.text()
|
||||
.new_text_layout(self.text.clone())
|
||||
.max_width(proposed_size.width)
|
||||
.text_color(self.color.clone())
|
||||
.build()
|
||||
.unwrap();
|
||||
let size = layout.size();
|
||||
self.layout = Some(layout);
|
||||
self.is_wrapped = needs_wrap;
|
||||
size
|
||||
} else {
|
||||
cx.widget_state.max_size
|
||||
}
|
||||
let mut lcx = parley::LayoutContext::new();
|
||||
let mut layout_builder = lcx.ranged_builder(cx.font_cx(), &self.text, 1.0);
|
||||
layout_builder.push_default(&parley::style::StyleProperty::Brush(ParleyBrush(
|
||||
Brush::Solid(Color::rgb8(255, 255, 255)),
|
||||
)));
|
||||
let mut layout = layout_builder.build();
|
||||
// Question for Chad: is this needed?
|
||||
layout.break_all_lines(None, parley::layout::Alignment::Start);
|
||||
self.layout = Some(layout);
|
||||
cx.widget_state.max_size
|
||||
}
|
||||
|
||||
fn align(&self, cx: &mut AlignCx, alignment: SingleAlignment) {
|
||||
if alignment.id() == FirstBaseline.id() {
|
||||
if let Some(metric) = self.layout.as_ref().unwrap().line_metric(0) {
|
||||
cx.aggregate(alignment, metric.baseline);
|
||||
}
|
||||
} else if alignment.id() == LastBaseline.id() {
|
||||
let i = self.layout.as_ref().unwrap().line_count() - 1;
|
||||
if let Some(metric) = self.layout.as_ref().unwrap().line_metric(i) {
|
||||
cx.aggregate(alignment, metric.y_offset + metric.baseline);
|
||||
}
|
||||
}
|
||||
}
|
||||
fn align(&self, cx: &mut AlignCx, alignment: SingleAlignment) {}
|
||||
|
||||
fn paint(&mut self, cx: &mut PaintCx) {
|
||||
cx.draw_text(self.layout.as_ref().unwrap(), Point::ZERO);
|
||||
fn paint(&mut self, cx: &mut PaintCx) -> Rendered {
|
||||
let mut fragment = SceneFragment::default();
|
||||
let mut builder = SceneBuilder::for_fragment(&mut fragment);
|
||||
if let Some(layout) = &self.layout {
|
||||
let transform = Affine::translate(40.0, 40.0);
|
||||
crate::text::render_text(&mut builder, transform, &layout);
|
||||
}
|
||||
Rendered(fragment)
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue