1use std::collections::HashMap;
2use std::sync::Arc;
3
4use bexa_ui_core::{
5 build_taffy, clear_active_widgets, collect_focus_paths, dispatch_event, dispatch_scroll,
6 draw_widgets, handle_scrollbar_event, release_scrollbar_drag, sync_styles,
7 try_start_scrollbar_drag, update_widget_measures, widget_mut_at_path, Renderer, Theme,
8 WidgetNode, WindowRequest, WindowRequests,
9};
10use bytemuck::{Pod, Zeroable};
11use glyphon::{
12 Attrs, Buffer, Cache, Color, Family, FontSystem, Metrics, Resolution, Shaping, SwashCache,
13 TextArea, TextAtlas, TextBounds, TextRenderer, Viewport, Weight,
14};
15use taffy::prelude::*;
16use wgpu::util::DeviceExt;
17use winit::event::{ElementState, Event, MouseButton, MouseScrollDelta, WindowEvent};
18use winit::event_loop::EventLoop;
19use winit::keyboard::{Key, ModifiersState, NamedKey};
20use winit::window::{Window, WindowBuilder, WindowId};
21
22#[repr(C)]
23#[derive(Copy, Clone, Pod, Zeroable)]
24struct Vertex {
25 position: [f32; 2],
26 uv: [f32; 2],
27 color: [f32; 4],
28 rect_center: [f32; 2],
29 rect_half: [f32; 2],
30 border_radius: f32,
31 border_width: f32,
32 border_color: [f32; 4],
33}
34
35impl Vertex {
36 fn layout() -> wgpu::VertexBufferLayout<'static> {
37 use std::mem::size_of;
38 wgpu::VertexBufferLayout {
39 array_stride: size_of::<Vertex>() as wgpu::BufferAddress,
40 step_mode: wgpu::VertexStepMode::Vertex,
41 attributes: &[
42 wgpu::VertexAttribute {
43 offset: 0,
44 shader_location: 0,
45 format: wgpu::VertexFormat::Float32x2,
46 },
47 wgpu::VertexAttribute {
48 offset: size_of::<[f32; 2]>() as u64,
49 shader_location: 1,
50 format: wgpu::VertexFormat::Float32x2,
51 },
52 wgpu::VertexAttribute {
53 offset: size_of::<[f32; 4]>() as u64,
54 shader_location: 2,
55 format: wgpu::VertexFormat::Float32x4,
56 },
57 wgpu::VertexAttribute {
58 offset: size_of::<[f32; 8]>() as u64,
59 shader_location: 3,
60 format: wgpu::VertexFormat::Float32x2,
61 },
62 wgpu::VertexAttribute {
63 offset: size_of::<[f32; 10]>() as u64,
64 shader_location: 4,
65 format: wgpu::VertexFormat::Float32x2,
66 },
67 wgpu::VertexAttribute {
68 offset: size_of::<[f32; 12]>() as u64,
69 shader_location: 5,
70 format: wgpu::VertexFormat::Float32,
71 },
72 wgpu::VertexAttribute {
73 offset: size_of::<[f32; 13]>() as u64,
74 shader_location: 6,
75 format: wgpu::VertexFormat::Float32,
76 },
77 wgpu::VertexAttribute {
78 offset: size_of::<[f32; 14]>() as u64,
79 shader_location: 7,
80 format: wgpu::VertexFormat::Float32x4,
81 },
82 ],
83 }
84 }
85}
86
87const SHADER_SRC: &str = r#"
88struct VertexOut {
89 @builtin(position) position: vec4<f32>,
90 @location(0) uv: vec2<f32>,
91 @location(1) color: vec4<f32>,
92 @location(2) rect_center: vec2<f32>,
93 @location(3) rect_half: vec2<f32>,
94 @location(4) border_radius: f32,
95 @location(5) border_width: f32,
96 @location(6) border_color: vec4<f32>,
97};
98
99@vertex
100fn vs_main(
101 @location(0) position: vec2<f32>,
102 @location(1) uv: vec2<f32>,
103 @location(2) color: vec4<f32>,
104 @location(3) rect_center: vec2<f32>,
105 @location(4) rect_half: vec2<f32>,
106 @location(5) border_radius: f32,
107 @location(6) border_width: f32,
108 @location(7) border_color: vec4<f32>,
109) -> VertexOut {
110 var out: VertexOut;
111 out.position = vec4<f32>(position, 0.0, 1.0);
112 out.uv = uv;
113 out.color = color;
114 out.rect_center = rect_center;
115 out.rect_half = rect_half;
116 out.border_radius = border_radius;
117 out.border_width = border_width;
118 out.border_color = border_color;
119 return out;
120}
121
122fn sdf_rounded_rect(p: vec2<f32>, half_size: vec2<f32>, radius: f32) -> f32 {
123 let r = min(radius, min(half_size.x, half_size.y));
124 let q = abs(p) - half_size + vec2<f32>(r, r);
125 return length(max(q, vec2<f32>(0.0, 0.0))) + min(max(q.x, q.y), 0.0) - r;
126}
127
128@fragment
129fn fs_main(in: VertexOut) -> @location(0) vec4<f32> {
130 let p = in.uv * in.rect_half;
131 let dist = sdf_rounded_rect(p, in.rect_half, in.border_radius);
132 let aa = 1.0;
133 let fill_alpha = 1.0 - smoothstep(-aa, 0.0, dist);
134
135 if fill_alpha < 0.001 {
136 discard;
137 }
138
139 var final_color = in.color;
140
141 if in.border_width > 0.0 {
142 let inner_dist = dist + in.border_width;
143 let border_mix = 1.0 - smoothstep(-aa, 0.0, inner_dist);
144 final_color = mix(in.border_color, in.color, border_mix);
145 }
146
147 final_color.a = final_color.a * fill_alpha;
148 return final_color;
149}
150"#;
151
152struct DrawBatch {
153 start: u32,
154 count: u32,
155 clip: Option<(f32, f32, f32, f32)>,
156}
157
158struct SharedGpu {
161 instance: wgpu::Instance,
162 device: wgpu::Device,
163 queue: wgpu::Queue,
164 render_pipeline: wgpu::RenderPipeline,
165 font_system: FontSystem,
166 swash_cache: SwashCache,
167 text_atlas: TextAtlas,
168 surface_format: wgpu::TextureFormat,
169}
170
171struct WindowState {
174 window: Arc<Window>,
175 surface: wgpu::Surface<'static>,
176 config: wgpu::SurfaceConfiguration,
177 size: winit::dpi::PhysicalSize<u32>,
178 vertex_buffer: wgpu::Buffer,
180 vertex_count: u32,
181 overlay_vertex_buffer: wgpu::Buffer,
182 overlay_vertex_count: u32,
183 draw_batches: Vec<DrawBatch>,
184 overlay_draw_batches: Vec<DrawBatch>,
185 text_renderer: TextRenderer,
186 overlay_text_renderer: TextRenderer,
187 text_viewport: Viewport,
188 text_buffers: Vec<Buffer>,
189 overlay_text_buffers: Vec<Buffer>,
190 root: WidgetNode,
192 taffy: TaffyTree,
193 root_node: NodeId,
194 renderer: Renderer,
195 focus_paths: Vec<Vec<usize>>,
196 focused_index: Option<usize>,
197 modifiers: ModifiersState,
198 cursor_pos: (f32, f32),
199 theme: Theme,
200 is_main: bool,
201}
202
203impl WindowState {
204 fn new(
205 window: Arc<Window>,
206 mut root: WidgetNode,
207 theme: Theme,
208 gpu: &mut SharedGpu,
209 is_main: bool,
210 ) -> Self {
211 let size = window.inner_size();
212
213 let surface = gpu.instance
214 .create_surface(window.clone())
215 .expect("create surface");
216
217 let config = wgpu::SurfaceConfiguration {
218 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
219 format: gpu.surface_format,
220 width: size.width.max(1),
221 height: size.height.max(1),
222 present_mode: wgpu::PresentMode::Fifo,
223 alpha_mode: wgpu::CompositeAlphaMode::Auto,
224 view_formats: vec![],
225 desired_maximum_frame_latency: 2,
226 };
227 surface.configure(&gpu.device, &config);
228
229 let text_cache = Cache::new(&gpu.device);
230 let text_viewport = Viewport::new(&gpu.device, &text_cache);
231 let text_renderer = TextRenderer::new(
232 &mut gpu.text_atlas,
233 &gpu.device,
234 wgpu::MultisampleState::default(),
235 None,
236 );
237 let overlay_text_renderer = TextRenderer::new(
238 &mut gpu.text_atlas,
239 &gpu.device,
240 wgpu::MultisampleState::default(),
241 None,
242 );
243
244 let mut taffy = TaffyTree::new();
245 let root_node = build_taffy(&mut root, &mut taffy);
246 let mut focus_paths = Vec::new();
247 collect_focus_paths(&root, &mut Vec::new(), &mut focus_paths);
248
249 let vertex_buffer = gpu.device.create_buffer(&wgpu::BufferDescriptor {
250 label: Some("Quad Vertex Buffer"),
251 size: 4,
252 usage: wgpu::BufferUsages::VERTEX,
253 mapped_at_creation: false,
254 });
255 let overlay_vertex_buffer = gpu.device.create_buffer(&wgpu::BufferDescriptor {
256 label: Some("Overlay Vertex Buffer"),
257 size: 4,
258 usage: wgpu::BufferUsages::VERTEX,
259 mapped_at_creation: false,
260 });
261
262 let mut ws = Self {
263 window,
264 surface,
265 config,
266 size,
267 vertex_buffer,
268 vertex_count: 0,
269 overlay_vertex_buffer,
270 overlay_vertex_count: 0,
271 draw_batches: Vec::new(),
272 overlay_draw_batches: Vec::new(),
273 text_renderer,
274 overlay_text_renderer,
275 text_viewport,
276 text_buffers: Vec::new(),
277 overlay_text_buffers: Vec::new(),
278 root,
279 taffy,
280 root_node,
281 renderer: Renderer::new(),
282 focus_paths,
283 focused_index: None,
284 modifiers: ModifiersState::default(),
285 cursor_pos: (0.0, 0.0),
286 theme,
287 is_main,
288 };
289
290 if !ws.focus_paths.is_empty() {
291 ws.set_focus(Some(0));
292 }
293
294 ws
295 }
296
297 fn resize(&mut self, new_size: winit::dpi::PhysicalSize<u32>, device: &wgpu::Device) {
298 if new_size.width == 0 || new_size.height == 0 {
299 return;
300 }
301 self.size = new_size;
302 self.config.width = new_size.width;
303 self.config.height = new_size.height;
304 self.surface.configure(device, &self.config);
305 }
306
307 fn update_layout(&mut self) {
308 let width = self.size.width as f32;
309 let height = self.size.height as f32;
310 if width == 0.0 || height == 0.0 {
311 return;
312 }
313 sync_styles(&mut self.root, &mut self.taffy, width, height, true);
314 let available_space = Size {
315 width: AvailableSpace::Definite(width),
316 height: AvailableSpace::Definite(height),
317 };
318 self.taffy
319 .compute_layout(self.root_node, available_space)
320 .expect("compute layout");
321 }
322
323 fn render(&mut self, gpu: &mut SharedGpu) -> Result<(), wgpu::SurfaceError> {
324 self.update_layout();
325
326 let viewport = (self.size.width as f32, self.size.height as f32);
327 self.renderer.clear();
328 draw_widgets(&self.root, &self.taffy, &mut self.renderer);
329
330 self.build_quad_vertices(viewport, &gpu.device);
331 self.build_overlay_vertices(viewport, &gpu.device);
332
333 self.text_viewport.update(
334 &gpu.queue,
335 Resolution {
336 width: self.config.width,
337 height: self.config.height,
338 },
339 );
340
341 let text_areas = build_text_areas(
342 &self.renderer.text_commands,
343 &mut self.text_buffers,
344 &mut gpu.font_system,
345 &mut self.renderer.text_measures,
346 );
347
348 update_widget_measures(&mut self.root, &self.renderer.text_measures);
349
350 self.text_renderer
351 .prepare(
352 &gpu.device,
353 &gpu.queue,
354 &mut gpu.font_system,
355 &mut gpu.text_atlas,
356 &self.text_viewport,
357 text_areas,
358 &mut gpu.swash_cache,
359 )
360 .expect("prepare text");
361
362 let output = self.surface.get_current_texture()?;
363 let view = output
364 .texture
365 .create_view(&wgpu::TextureViewDescriptor::default());
366
367 let mut encoder =
368 gpu.device
369 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
370 label: Some("Render Encoder"),
371 });
372
373 {
374 let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
375 label: Some("Render Pass"),
376 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
377 view: &view,
378 depth_slice: None,
379 resolve_target: None,
380 ops: wgpu::Operations {
381 load: wgpu::LoadOp::Clear(wgpu::Color {
382 r: self.theme.background[0] as f64,
383 g: self.theme.background[1] as f64,
384 b: self.theme.background[2] as f64,
385 a: 1.0,
386 }),
387 store: wgpu::StoreOp::Store,
388 },
389 })],
390 depth_stencil_attachment: None,
391 occlusion_query_set: None,
392 timestamp_writes: None,
393 multiview_mask: None,
394 });
395
396 let sw = self.size.width;
397 let sh = self.size.height;
398
399 render_pass.set_pipeline(&gpu.render_pipeline);
401 render_pass.set_vertex_buffer(0, self.vertex_buffer.slice(..));
402 for batch in &self.draw_batches {
403 if let Some((cx, cy, cw, ch)) = batch.clip {
404 let sx = (cx.max(0.0) as u32).min(sw);
405 let sy = (cy.max(0.0) as u32).min(sh);
406 let right = ((cx + cw).max(0.0) as u32).min(sw);
407 let bottom = ((cy + ch).max(0.0) as u32).min(sh);
408 let swidth = right.saturating_sub(sx);
409 let sheight = bottom.saturating_sub(sy);
410 if swidth == 0 || sheight == 0 {
411 continue;
412 }
413 render_pass.set_scissor_rect(sx, sy, swidth, sheight);
414 } else {
415 render_pass.set_scissor_rect(0, 0, sw, sh);
416 }
417 render_pass.draw(batch.start..batch.start + batch.count, 0..1);
418 }
419
420 render_pass.set_scissor_rect(0, 0, sw, sh);
422 self.text_renderer
423 .render(&gpu.text_atlas, &self.text_viewport, &mut render_pass)
424 .expect("render text");
425
426 if self.overlay_vertex_count > 0 {
428 render_pass.set_pipeline(&gpu.render_pipeline);
429 render_pass.set_vertex_buffer(0, self.overlay_vertex_buffer.slice(..));
430 for batch in &self.overlay_draw_batches {
431 if let Some((cx, cy, cw, ch)) = batch.clip {
432 let sx = (cx.max(0.0) as u32).min(sw);
433 let sy = (cy.max(0.0) as u32).min(sh);
434 let right = ((cx + cw).max(0.0) as u32).min(sw);
435 let bottom = ((cy + ch).max(0.0) as u32).min(sh);
436 let swidth = right.saturating_sub(sx);
437 let sheight = bottom.saturating_sub(sy);
438 if swidth == 0 || sheight == 0 {
439 continue;
440 }
441 render_pass.set_scissor_rect(sx, sy, swidth, sheight);
442 } else {
443 render_pass.set_scissor_rect(0, 0, sw, sh);
444 }
445 render_pass.draw(batch.start..batch.start + batch.count, 0..1);
446 }
447 }
448 }
449
450 if !self.renderer.overlay_text_commands.is_empty() {
452 let overlay_text_areas = build_text_areas(
453 &self.renderer.overlay_text_commands,
454 &mut self.overlay_text_buffers,
455 &mut gpu.font_system,
456 &mut vec![],
457 );
458
459 self.overlay_text_renderer
460 .prepare(
461 &gpu.device,
462 &gpu.queue,
463 &mut gpu.font_system,
464 &mut gpu.text_atlas,
465 &self.text_viewport,
466 overlay_text_areas,
467 &mut gpu.swash_cache,
468 )
469 .expect("prepare overlay text");
470
471 {
472 let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
473 label: Some("Overlay Text Pass"),
474 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
475 view: &view,
476 depth_slice: None,
477 resolve_target: None,
478 ops: wgpu::Operations {
479 load: wgpu::LoadOp::Load,
480 store: wgpu::StoreOp::Store,
481 },
482 })],
483 depth_stencil_attachment: None,
484 occlusion_query_set: None,
485 timestamp_writes: None,
486 multiview_mask: None,
487 });
488
489 let sw = self.size.width;
490 let sh = self.size.height;
491 render_pass.set_scissor_rect(0, 0, sw, sh);
492 self.overlay_text_renderer
493 .render(&gpu.text_atlas, &self.text_viewport, &mut render_pass)
494 .expect("render overlay text");
495 }
496 }
497
498 gpu.queue.submit(Some(encoder.finish()));
499 output.present();
500 gpu.text_atlas.trim();
501
502 Ok(())
503 }
504
505 fn handle_window_event(&mut self, event: &WindowEvent) {
506 if let WindowEvent::CursorMoved { position, .. } = event {
507 self.cursor_pos = (position.x as f32, position.y as f32);
508 }
509
510 if handle_scrollbar_event(&mut self.root, &self.taffy, event) {
511 return;
512 }
513
514 if let WindowEvent::MouseInput {
515 state: ElementState::Pressed,
516 button: MouseButton::Left,
517 ..
518 } = event
519 {
520 let (cx, cy) = self.cursor_pos;
521 if try_start_scrollbar_drag(&mut self.root, &self.taffy, cx, cy) {
522 return;
523 }
524 }
525
526 if let WindowEvent::MouseInput {
527 state: ElementState::Released,
528 button: MouseButton::Left,
529 ..
530 } = event
531 {
532 release_scrollbar_drag(&mut self.root);
533 }
534
535 let mut path = Vec::new();
536 if let Some(consumed_path) =
537 dispatch_event(&mut self.root, &self.taffy, event, &mut path)
538 {
539 if matches!(
540 event,
541 WindowEvent::MouseInput {
542 state: ElementState::Pressed,
543 button: MouseButton::Left,
544 ..
545 }
546 ) {
547 self.set_focus_by_path(&consumed_path);
548 }
549 }
550 }
551
552 fn handle_mouse_wheel(&mut self, delta: MouseScrollDelta) {
553 let delta_y = match delta {
554 MouseScrollDelta::LineDelta(_, y) => y * 40.0,
555 MouseScrollDelta::PixelDelta(d) => d.y as f32,
556 };
557 let (cx, cy) = self.cursor_pos;
558 dispatch_scroll(&mut self.root, delta_y, cx, cy, &self.taffy);
559 }
560
561 fn handle_keyboard_input(&mut self, event: &winit::event::KeyEvent) {
562 if let Some(idx) = self.focused_index {
563 if let Some(path) = self.focus_paths.get(idx).cloned() {
564 if let Some(widget) = widget_mut_at_path(&mut self.root, &path) {
565 if widget.handle_key_event(event, self.modifiers) {
566 return;
567 }
568 }
569 }
570 }
571
572 match &event.logical_key {
573 Key::Named(NamedKey::Tab) => {
574 let reverse = self.modifiers.shift_key();
575 self.focus_next(reverse);
576 }
577 Key::Named(NamedKey::Enter) | Key::Named(NamedKey::Space) => {
578 self.activate_focused();
579 }
580 Key::Named(NamedKey::Escape) => {
581 self.clear_active();
582 }
583 _ => {}
584 }
585 }
586
587 fn focus_next(&mut self, reverse: bool) {
588 if self.focus_paths.is_empty() {
589 return;
590 }
591 let count = self.focus_paths.len();
592 let current = self.focused_index.unwrap_or(0);
593 let next = if reverse {
594 (current + count - 1) % count
595 } else {
596 (current + 1) % count
597 };
598 self.set_focus(Some(next));
599 }
600
601 fn activate_focused(&mut self) {
602 let Some(index) = self.focused_index else {
603 return;
604 };
605 if let Some(path) = self.focus_paths.get(index).cloned() {
606 if let Some(widget) = widget_mut_at_path(&mut self.root, &path) {
607 widget.activate();
608 }
609 }
610 }
611
612 fn clear_active(&mut self) {
613 clear_active_widgets(&mut self.root);
614 }
615
616 fn set_focus(&mut self, index: Option<usize>) {
617 self.focused_index = index;
618 for (i, path) in self.focus_paths.iter().enumerate() {
619 if let Some(widget) = widget_mut_at_path(&mut self.root, path) {
620 widget.set_focus(Some(i) == index);
621 }
622 }
623 }
624
625 fn set_focus_by_path(&mut self, path: &[usize]) {
626 if let Some(index) = self.focus_paths.iter().position(|p| p == path) {
627 self.set_focus(Some(index));
628 }
629 }
630
631 fn build_quad_vertices(&mut self, viewport: (f32, f32), device: &wgpu::Device) {
632 let mut vertices = Vec::with_capacity(self.renderer.quad_commands.len() * 6);
633 let (vw, vh) = viewport;
634
635 self.draw_batches.clear();
636 let mut current_clip: Option<(f32, f32, f32, f32)> = None;
637 let mut batch_start: u32 = 0;
638
639 for cmd in &self.renderer.quad_commands {
640 if cmd.clip != current_clip {
641 let vert_count = vertices.len() as u32;
642 if vert_count > batch_start {
643 self.draw_batches.push(DrawBatch {
644 start: batch_start,
645 count: vert_count - batch_start,
646 clip: current_clip,
647 });
648 }
649 current_clip = cmd.clip;
650 batch_start = vert_count;
651 }
652
653 let (x, y, w, h) = cmd.rect;
654 let x0 = (x / vw) * 2.0 - 1.0;
655 let x1 = ((x + w) / vw) * 2.0 - 1.0;
656 let y0 = 1.0 - (y / vh) * 2.0;
657 let y1 = 1.0 - ((y + h) / vh) * 2.0;
658 let cx = x + w * 0.5;
659 let cy = y + h * 0.5;
660 let hx = w * 0.5;
661 let hy = h * 0.5;
662
663 let make_vertex = |px: f32, py: f32, u: f32, v: f32| Vertex {
664 position: [px, py],
665 uv: [u, v],
666 color: cmd.color,
667 rect_center: [cx, cy],
668 rect_half: [hx, hy],
669 border_radius: cmd.border_radius,
670 border_width: cmd.border_width,
671 border_color: cmd.border_color,
672 };
673
674 vertices.push(make_vertex(x0, y1, -1.0, 1.0));
675 vertices.push(make_vertex(x1, y1, 1.0, 1.0));
676 vertices.push(make_vertex(x1, y0, 1.0, -1.0));
677 vertices.push(make_vertex(x0, y1, -1.0, 1.0));
678 vertices.push(make_vertex(x1, y0, 1.0, -1.0));
679 vertices.push(make_vertex(x0, y0, -1.0, -1.0));
680 }
681
682 let vert_count = vertices.len() as u32;
683 if vert_count > batch_start {
684 self.draw_batches.push(DrawBatch {
685 start: batch_start,
686 count: vert_count - batch_start,
687 clip: current_clip,
688 });
689 }
690
691 self.vertex_count = vertices.len() as u32;
692 if vertices.is_empty() {
693 self.vertex_buffer = device.create_buffer(&wgpu::BufferDescriptor {
694 label: Some("Quad Vertex Buffer"),
695 size: 4,
696 usage: wgpu::BufferUsages::VERTEX,
697 mapped_at_creation: false,
698 });
699 } else {
700 self.vertex_buffer =
701 device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
702 label: Some("Quad Vertex Buffer"),
703 contents: bytemuck::cast_slice(&vertices),
704 usage: wgpu::BufferUsages::VERTEX,
705 });
706 }
707 }
708
709 fn build_overlay_vertices(&mut self, viewport: (f32, f32), device: &wgpu::Device) {
710 let mut vertices =
711 Vec::with_capacity(self.renderer.overlay_quad_commands.len() * 6);
712 let (vw, vh) = viewport;
713
714 self.overlay_draw_batches.clear();
715 let mut current_clip: Option<(f32, f32, f32, f32)> = None;
716 let mut batch_start: u32 = 0;
717
718 for cmd in &self.renderer.overlay_quad_commands {
719 if cmd.clip != current_clip {
720 let vert_count = vertices.len() as u32;
721 if vert_count > batch_start {
722 self.overlay_draw_batches.push(DrawBatch {
723 start: batch_start,
724 count: vert_count - batch_start,
725 clip: current_clip,
726 });
727 }
728 current_clip = cmd.clip;
729 batch_start = vert_count;
730 }
731
732 let (x, y, w, h) = cmd.rect;
733 let x0 = (x / vw) * 2.0 - 1.0;
734 let x1 = ((x + w) / vw) * 2.0 - 1.0;
735 let y0 = 1.0 - (y / vh) * 2.0;
736 let y1 = 1.0 - ((y + h) / vh) * 2.0;
737 let cx = x + w * 0.5;
738 let cy = y + h * 0.5;
739 let hx = w * 0.5;
740 let hy = h * 0.5;
741
742 let make_vertex = |px: f32, py: f32, u: f32, v: f32| Vertex {
743 position: [px, py],
744 uv: [u, v],
745 color: cmd.color,
746 rect_center: [cx, cy],
747 rect_half: [hx, hy],
748 border_radius: cmd.border_radius,
749 border_width: cmd.border_width,
750 border_color: cmd.border_color,
751 };
752
753 vertices.push(make_vertex(x0, y1, -1.0, 1.0));
754 vertices.push(make_vertex(x1, y1, 1.0, 1.0));
755 vertices.push(make_vertex(x1, y0, 1.0, -1.0));
756 vertices.push(make_vertex(x0, y1, -1.0, 1.0));
757 vertices.push(make_vertex(x1, y0, 1.0, -1.0));
758 vertices.push(make_vertex(x0, y0, -1.0, -1.0));
759 }
760
761 let vert_count = vertices.len() as u32;
762 if vert_count > batch_start {
763 self.overlay_draw_batches.push(DrawBatch {
764 start: batch_start,
765 count: vert_count - batch_start,
766 clip: current_clip,
767 });
768 }
769
770 self.overlay_vertex_count = vertices.len() as u32;
771 if vertices.is_empty() {
772 self.overlay_vertex_buffer = device.create_buffer(&wgpu::BufferDescriptor {
773 label: Some("Overlay Vertex Buffer"),
774 size: 4,
775 usage: wgpu::BufferUsages::VERTEX,
776 mapped_at_creation: false,
777 });
778 } else {
779 self.overlay_vertex_buffer =
780 device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
781 label: Some("Overlay Vertex Buffer"),
782 contents: bytemuck::cast_slice(&vertices),
783 usage: wgpu::BufferUsages::VERTEX,
784 });
785 }
786 }
787}
788
789pub struct App {
792 root: WidgetNode,
793 theme: Theme,
794 title: String,
795 window_requests: Option<WindowRequests>,
796}
797
798impl App {
799 pub fn new(root: WidgetNode) -> Self {
800 Self {
801 root,
802 theme: Theme::ocean(),
803 title: "BexaUI".to_string(),
804 window_requests: None,
805 }
806 }
807
808 pub fn theme(mut self, theme: Theme) -> Self {
809 self.theme = theme;
810 self
811 }
812
813 pub fn title(mut self, title: impl Into<String>) -> Self {
814 self.title = title.into();
815 self
816 }
817
818 pub fn with_requests(mut self, requests: WindowRequests) -> Self {
819 self.window_requests = Some(requests);
820 self
821 }
822
823 pub fn window_requests() -> WindowRequests {
825 bexa_ui_core::create_window_requests()
826 }
827
828 pub fn run(self) {
829 let event_loop = EventLoop::new().expect("create event loop");
830
831 let window = Arc::new(
833 WindowBuilder::new()
834 .with_title(&self.title)
835 .build(&event_loop)
836 .expect("create window"),
837 );
838
839 let mut gpu = pollster::block_on(init_gpu(window.clone()));
841
842 let main_ws = WindowState::new(window.clone(), self.root, self.theme, &mut gpu, true);
844 let main_id = main_ws.window.id();
845
846 let mut windows: HashMap<WindowId, WindowState> = HashMap::new();
847 windows.insert(main_id, main_ws);
848
849 let window_requests = self.window_requests;
850
851 event_loop
852 .run(move |event, elwt| {
853 elwt.set_control_flow(winit::event_loop::ControlFlow::Poll);
854 match event {
855 Event::WindowEvent {
856 event: ref win_event,
857 window_id,
858 } => {
859 if let Some(ws) = windows.get_mut(&window_id) {
860 match win_event {
861 WindowEvent::CloseRequested => {
862 if ws.is_main {
863 elwt.exit();
864 } else {
865 windows.remove(&window_id);
866 }
867 }
868 WindowEvent::Resized(size) => {
869 ws.resize(*size, &gpu.device);
870 }
871 WindowEvent::CursorMoved { .. }
872 | WindowEvent::MouseInput { .. } => {
873 ws.handle_window_event(win_event);
874 }
875 WindowEvent::MouseWheel { delta, .. } => {
876 ws.handle_mouse_wheel(*delta);
877 }
878 WindowEvent::KeyboardInput { event, .. } => {
879 if event.state == ElementState::Pressed {
880 ws.handle_keyboard_input(event);
881 }
882 }
883 WindowEvent::ModifiersChanged(modifiers) => {
884 ws.modifiers = modifiers.state();
885 }
886 WindowEvent::RedrawRequested => {
887 match ws.render(&mut gpu) {
888 Ok(()) => {}
889 Err(wgpu::SurfaceError::Lost) => {
890 let size = ws.size;
891 ws.resize(size, &gpu.device);
892 }
893 Err(wgpu::SurfaceError::OutOfMemory) => elwt.exit(),
894 Err(_) => {}
895 }
896 }
897 _ => {}
898 }
899 }
900 }
901 Event::AboutToWait => {
902 if let Some(ref reqs) = window_requests {
904 let pending: Vec<WindowRequest> = {
905 let mut lock = reqs.lock().unwrap();
906 lock.drain(..).collect()
907 };
908 for req in pending {
909 let new_window = Arc::new(
910 WindowBuilder::new()
911 .with_title(&req.title)
912 .with_inner_size(winit::dpi::LogicalSize::new(
913 req.width, req.height,
914 ))
915 .build(elwt)
916 .expect("create child window"),
917 );
918 let new_id = new_window.id();
919 let ws = WindowState::new(
920 new_window,
921 req.root,
922 req.theme,
923 &mut gpu,
924 false,
925 );
926 windows.insert(new_id, ws);
927 }
928 }
929
930 for ws in windows.values() {
932 ws.window.request_redraw();
933 }
934 }
935 _ => {}
936 }})
937 .expect("run event loop");
938 }
939}
940
941async fn init_gpu(window: Arc<Window>) -> SharedGpu {
944 let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor {
945 backends: wgpu::Backends::PRIMARY,
946 ..Default::default()
947 });
948
949 let surface = instance
950 .create_surface(window.clone())
951 .expect("create surface");
952 let adapter = instance
953 .request_adapter(&wgpu::RequestAdapterOptions {
954 power_preference: wgpu::PowerPreference::HighPerformance,
955 compatible_surface: Some(&surface),
956 force_fallback_adapter: false,
957 })
958 .await
959 .expect("find GPU adapter");
960
961 let (device, queue) = adapter
962 .request_device(&wgpu::DeviceDescriptor {
963 label: None,
964 required_features: wgpu::Features::empty(),
965 required_limits: wgpu::Limits::default(),
966 ..Default::default()
967 })
968 .await
969 .expect("create device");
970
971 let surface_caps = surface.get_capabilities(&adapter);
972 let surface_format = surface_caps
973 .formats
974 .iter()
975 .copied()
976 .find(|format| format.is_srgb())
977 .unwrap_or(surface_caps.formats[0]);
978
979 let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
980 label: Some("Quad SDF Shader"),
981 source: wgpu::ShaderSource::Wgsl(SHADER_SRC.into()),
982 });
983
984 let render_pipeline_layout =
985 device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
986 label: Some("Quad Pipeline Layout"),
987 bind_group_layouts: &[],
988 immediate_size: 0,
989 });
990
991 let render_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
992 label: Some("Quad Pipeline"),
993 layout: Some(&render_pipeline_layout),
994 vertex: wgpu::VertexState {
995 module: &shader,
996 entry_point: Some("vs_main"),
997 buffers: &[Vertex::layout()],
998 compilation_options: Default::default(),
999 },
1000 fragment: Some(wgpu::FragmentState {
1001 module: &shader,
1002 entry_point: Some("fs_main"),
1003 targets: &[Some(wgpu::ColorTargetState {
1004 format: surface_format,
1005 blend: Some(wgpu::BlendState::ALPHA_BLENDING),
1006 write_mask: wgpu::ColorWrites::ALL,
1007 })],
1008 compilation_options: Default::default(),
1009 }),
1010 primitive: wgpu::PrimitiveState {
1011 topology: wgpu::PrimitiveTopology::TriangleList,
1012 strip_index_format: None,
1013 front_face: wgpu::FrontFace::Ccw,
1014 cull_mode: None,
1015 ..Default::default()
1016 },
1017 depth_stencil: None,
1018 multisample: wgpu::MultisampleState::default(),
1019 multiview_mask: None,
1020 cache: None,
1021 });
1022
1023 let mut font_system = FontSystem::new();
1024 let nerd_font_data = include_bytes!("../assets/fonts/SymbolsNerdFont-Regular.ttf");
1025 font_system
1026 .db_mut()
1027 .load_font_data(nerd_font_data.to_vec());
1028 let swash_cache = SwashCache::new();
1029 let text_cache = Cache::new(&device);
1030 let _text_viewport = Viewport::new(&device, &text_cache);
1031 let text_atlas = TextAtlas::new(&device, &queue, &text_cache, surface_format);
1032
1033 SharedGpu {
1034 instance,
1035 device,
1036 queue,
1037 render_pipeline,
1038 font_system,
1039 swash_cache,
1040 text_atlas,
1041 surface_format,
1042 }
1043}
1044
1045fn build_text_areas<'a>(
1048 commands: &'a [bexa_ui_core::TextCommand],
1049 text_buffers: &'a mut Vec<Buffer>,
1050 font_system: &mut FontSystem,
1051 measures_out: &mut Vec<Vec<f32>>,
1052) -> Vec<TextArea<'a>> {
1053 let mut areas = Vec::with_capacity(commands.len());
1054 measures_out.clear();
1055 measures_out.resize(commands.len(), vec![]);
1056
1057 if text_buffers.len() < commands.len() {
1058 for _ in text_buffers.len()..commands.len() {
1059 text_buffers.push(Buffer::new(font_system, Metrics::new(16.0, 20.0)));
1060 }
1061 }
1062
1063 for (idx, command) in commands.iter().enumerate() {
1064 let buffer = &mut text_buffers[idx];
1065 *buffer = Buffer::new(font_system, command.metrics);
1066
1067 let family = match &command.font_family {
1068 Some(name) => Family::Name(name),
1069 None => Family::SansSerif,
1070 };
1071 let attrs = Attrs::new().family(family).weight(Weight::MEDIUM);
1072
1073 buffer.set_text(
1074 font_system,
1075 &command.text,
1076 &attrs,
1077 Shaping::Advanced,
1078 Some(command.align),
1079 );
1080 buffer.set_size(
1081 font_system,
1082 Some(command.bounds.0),
1083 Some(command.bounds.1),
1084 );
1085 buffer.shape_until_scroll(font_system, false);
1086
1087 if !command.measure_chars.is_empty() {
1088 let mut results = Vec::with_capacity(command.measure_chars.len());
1089 for &char_pos in &command.measure_chars {
1090 let byte_pos = command
1091 .text
1092 .char_indices()
1093 .nth(char_pos)
1094 .map(|(i, _)| i)
1095 .unwrap_or(command.text.len());
1096
1097 let mut width = 0.0f32;
1098 for run in buffer.layout_runs() {
1099 for glyph in run.glyphs.iter() {
1100 if glyph.start < byte_pos {
1101 width = glyph.x + glyph.w;
1102 }
1103 }
1104 }
1105 results.push(width);
1106 }
1107 measures_out[idx] = results;
1108 }
1109 }
1110
1111 for (idx, command) in commands.iter().enumerate() {
1112 let buffer = &text_buffers[idx];
1113
1114 let mut left = command.pos.0 as i32;
1115 let mut top = command.pos.1 as i32;
1116 let mut right = (command.pos.0 + command.bounds.0) as i32;
1117 let mut bottom = (command.pos.1 + command.bounds.1) as i32;
1118
1119 if let Some((cx, cy, cw, ch)) = command.clip {
1120 left = left.max(cx as i32);
1121 top = top.max(cy as i32);
1122 right = right.min((cx + cw) as i32);
1123 bottom = bottom.min((cy + ch) as i32);
1124 }
1125
1126 if right <= left || bottom <= top {
1127 continue;
1128 }
1129
1130 areas.push(TextArea {
1131 buffer,
1132 left: command.pos.0,
1133 top: command.pos.1,
1134 scale: 1.0,
1135 bounds: TextBounds {
1136 left,
1137 top,
1138 right,
1139 bottom,
1140 },
1141 default_color: Color::rgb(command.color[0], command.color[1], command.color[2]),
1142 custom_glyphs: &[],
1143 });
1144 }
1145
1146 areas
1147}