1use std::{
2 cell::{Cell, RefCell},
3 ptr::{null, null_mut},
4};
5
6use compio_log::*;
7use image::DynamicImage;
8use inherit_methods_macro::inherit_methods;
9use objc2::{
10 DeclaredClass, MainThreadOnly, define_class, msg_send,
11 rc::{Allocated, Retained},
12};
13use objc2_app_kit::{NSEvent, NSEventType, NSGraphicsContext, NSView};
14use objc2_core_foundation::{CFRange, CFRetained, CGAffineTransform};
15use objc2_core_graphics::{CGAffineTransformMake, CGColor, CGMutablePath, CGPath, kCGColorWhite};
16use objc2_core_text::CTFramesetter;
17use objc2_foundation::{MainThreadMarker, NSRect, NSSize};
18use winio_callback::Callback;
19use winio_handle::AsContainer;
20use winio_primitive::{
21 DrawingFont, HAlign, MouseButton, Point, Rect, Size, Transform, VAlign, Vector,
22};
23
24use crate::{
25 Brush, DrawAction, DrawingImage, Error, GlobalRuntime, Pen, Result, Widget, catch,
26 create_attr_str, from_cgsize, transform_cgpoint, transform_point, transform_rect,
27};
28
29#[derive(Debug)]
30pub(crate) struct CanvasImpl {
31 view: Retained<CanvasView>,
32 handle: Widget,
33}
34#[inherit_methods(from = "self.handle")]
35impl CanvasImpl {
36 pub fn new(parent: impl AsContainer) -> Result<Self> {
37 let parent = parent.as_container();
38 let view = catch(|| CanvasView::new(parent.as_app_kit().mtm()))?;
39 let handle = Widget::from_nsview(parent, view.clone().into_super())?;
40 Ok(Self { view, handle })
41 }
42
43 pub fn is_visible(&self) -> Result<bool>;
44
45 pub fn set_visible(&mut self, v: bool) -> Result<()>;
46
47 pub fn is_enabled(&self) -> Result<bool>;
48
49 pub fn set_enabled(&mut self, v: bool) -> Result<()>;
50
51 pub fn loc(&self) -> Result<Point>;
52
53 pub fn set_loc(&mut self, p: Point) -> Result<()>;
54
55 pub fn size(&self) -> Result<Size>;
56
57 pub fn set_size(&mut self, v: Size) -> Result<()>;
58
59 pub fn tooltip(&self) -> Result<String>;
60
61 pub fn set_tooltip(&mut self, s: impl AsRef<str>) -> Result<()>;
62
63 pub async fn wait_mouse_down(&self) -> MouseButton {
64 self.view.ivars().mouse_down.wait().await
65 }
66
67 pub async fn wait_mouse_up(&self) -> MouseButton {
68 self.view.ivars().mouse_up.wait().await
69 }
70
71 pub async fn wait_mouse_move(&self) -> Point {
72 self.view.ivars().mouse_move.wait().await;
73 self.view
74 .window()
75 .map(|w| {
76 let p = w.mouseLocationOutsideOfEventStream();
77 transform_cgpoint(self.size().unwrap_or_default(), p)
78 })
79 .unwrap_or_default()
80 }
81
82 pub async fn wait_mouse_wheel(&self) -> Vector {
83 self.view.ivars().mouse_scroll.wait().await
84 }
85}
86
87winio_handle::impl_as_widget!(CanvasImpl, handle);
88
89#[derive(Debug)]
90pub struct Canvas {
91 handle: CanvasImpl,
92}
93
94#[inherit_methods(from = "self.handle")]
95impl Canvas {
96 pub fn new(parent: impl AsContainer) -> Result<Self> {
97 let handle = CanvasImpl::new(parent)?;
98 Ok(Self { handle })
99 }
100
101 pub fn is_visible(&self) -> Result<bool>;
102
103 pub fn set_visible(&mut self, v: bool) -> Result<()>;
104
105 pub fn is_enabled(&self) -> Result<bool>;
106
107 pub fn set_enabled(&mut self, v: bool) -> Result<()>;
108
109 pub fn loc(&self) -> Result<Point>;
110
111 pub fn set_loc(&mut self, p: Point) -> Result<()>;
112
113 pub fn size(&self) -> Result<Size>;
114
115 pub fn set_size(&mut self, v: Size) -> Result<()>;
116
117 pub fn tooltip(&self) -> Result<String>;
118
119 pub fn set_tooltip(&mut self, s: impl AsRef<str>) -> Result<()>;
120
121 pub fn context(&mut self) -> Result<DrawingContext<'_>> {
122 Ok(DrawingContext {
123 size: self.size()?,
124 actions: self.handle.view.ivars().take_buffer(),
125 canvas: self,
126 transform: Transform::identity(),
127 ended: false,
128 })
129 }
130
131 pub async fn wait_mouse_down(&self) -> MouseButton {
132 self.handle.wait_mouse_down().await
133 }
134
135 pub async fn wait_mouse_up(&self) -> MouseButton {
136 self.handle.wait_mouse_up().await
137 }
138
139 pub async fn wait_mouse_move(&self) -> Point {
140 self.handle.wait_mouse_move().await
141 }
142
143 pub async fn wait_mouse_wheel(&self) -> Vector {
144 self.handle.wait_mouse_wheel().await
145 }
146}
147
148winio_handle::impl_as_widget!(Canvas, handle);
149
150fn draw_rect(actions: &[DrawAction], _rect: NSRect, factor: f64) {
151 let Some(ns_context) = NSGraphicsContext::currentContext() else {
152 error!("Cannot get current NSGraphicsContext");
153 return;
154 };
155 let context = ns_context.CGContext();
156 DrawAction::draw_rect(actions, &context, factor);
157}
158
159#[derive(Debug, Default)]
160struct CanvasViewIvars {
161 mouse_down: Callback<MouseButton>,
162 mouse_up: Callback<MouseButton>,
163 mouse_move: Callback,
164 mouse_scroll: Callback<Vector>,
165 actions: RefCell<Vec<DrawAction>>,
166 actions_buf: RefCell<Vec<DrawAction>>,
168 factor: Cell<f64>,
169}
170
171impl CanvasViewIvars {
172 pub fn take_buffer(&self) -> Vec<DrawAction> {
173 std::mem::take(&mut self.actions_buf.borrow_mut())
174 }
175
176 pub fn swap_buffer(&self, buf: &mut Vec<DrawAction>) {
177 {
178 let mut actions = self.actions.borrow_mut();
179 std::mem::swap::<Vec<DrawAction>>(&mut actions, buf);
180 }
181 {
182 let mut actions_buf = self.actions_buf.borrow_mut();
183 std::mem::swap::<Vec<DrawAction>>(&mut actions_buf, buf);
184 actions_buf.clear();
185 }
186 }
187}
188
189define_class! {
190 #[unsafe(super(NSView))]
191 #[name = "WinioCanvasView"]
192 #[ivars = CanvasViewIvars]
193 #[thread_kind = MainThreadOnly]
194 #[derive(Debug)]
195 struct CanvasView;
196
197 #[allow(non_snake_case)]
198 impl CanvasView {
199 #[unsafe(method_id(init))]
200 fn init(this: Allocated<Self>) -> Option<Retained<Self>> {
201 let this = this.set_ivars(CanvasViewIvars::default());
202 unsafe { msg_send![super(this), init] }
203 }
204
205 #[unsafe(method(acceptsFirstResponder))]
206 unsafe fn acceptsFirstResponder(&self) -> bool {
207 true
208 }
209
210 #[unsafe(method(drawRect:))]
211 unsafe fn drawRect(&self, rect: NSRect) {
212 let ivars = self.ivars();
213 draw_rect(&ivars.actions.borrow(), rect, ivars.factor.get())
214 }
215
216 #[unsafe(method(mouseDown:))]
217 unsafe fn mouseDown(&self, event: &NSEvent) {
218 self.ivars().mouse_down.signal::<GlobalRuntime>(mouse_button(event));
219 }
220
221 #[unsafe(method(mouseUp:))]
222 unsafe fn mouseUp(&self, event: &NSEvent) {
223 self.ivars().mouse_up.signal::<GlobalRuntime>(mouse_button(event));
224 }
225
226 #[unsafe(method(mouseDragged:))]
227 unsafe fn mouseDragged(&self, _event: &NSEvent) {
228 self.ivars().mouse_move.signal::<GlobalRuntime>(());
229 }
230
231 #[unsafe(method(mouseMoved:))]
232 unsafe fn mouseMoved(&self, _event: &NSEvent) {
233 self.ivars().mouse_move.signal::<GlobalRuntime>(());
234 }
235
236 #[unsafe(method(scrollWheel:))]
237 unsafe fn scrollWheel(&self, event: &NSEvent) {
238 if event.r#type() == NSEventType::ScrollWheel {
239 self.ivars().mouse_scroll.signal::<GlobalRuntime>(
240 Vector::new(event.scrollingDeltaX(), event.scrollingDeltaY())
241 );
242 }
243 }
244 }
245}
246
247impl CanvasView {
248 pub fn new(mtm: MainThreadMarker) -> Retained<Self> {
249 unsafe { msg_send![mtm.alloc::<Self>(), init] }
250 }
251}
252
253fn mouse_button(event: &NSEvent) -> MouseButton {
254 match event.r#type() {
255 NSEventType::LeftMouseDown | NSEventType::LeftMouseUp => MouseButton::Left,
256 NSEventType::RightMouseDown | NSEventType::RightMouseUp => MouseButton::Right,
257 _ => MouseButton::Other,
258 }
259}
260
261pub struct DrawingContext<'a> {
262 size: Size,
263 actions: Vec<DrawAction>,
264 canvas: &'a mut Canvas,
265 transform: Transform,
266 ended: bool,
267}
268
269impl Drop for DrawingContext<'_> {
270 fn drop(&mut self) {
271 if let Err(_e) = self.end() {
272 error!("Error dropping DrawingContext: {_e:?}");
273 }
274 }
275}
276
277impl DrawingContext<'_> {
278 fn end(&mut self) -> Result<()> {
279 if !self.ended {
280 let ivars = self.canvas.handle.view.ivars();
281 ivars.swap_buffer(&mut self.actions);
282 ivars.factor.set(
283 self.canvas
284 .handle
285 .view
286 .window()
287 .map(|w| w.backingScaleFactor())
288 .unwrap_or(1.0),
289 );
290 catch(|| self.canvas.handle.view.setNeedsDisplay(true))?;
291 self.ended = true;
292 }
293 Ok(())
294 }
295
296 pub fn close(mut self) -> Result<()> {
297 self.end()
298 }
299
300 pub fn set_transform(&mut self, transform: Transform) -> Result<()> {
301 self.transform = transform;
302 self.actions.push(DrawAction::Transform(CGAffineTransform {
303 a: transform.m11,
304 b: transform.m12,
305 c: transform.m21,
306 d: transform.m22,
307 tx: transform.m31,
308 ty: transform.m32,
309 }));
310 Ok(())
311 }
312
313 pub fn transform(&self) -> Result<Transform> {
314 Ok(self.transform)
315 }
316
317 fn draw(&mut self, pen: impl Pen, path: CFRetained<CGPath>) -> Result<()> {
318 self.actions.push(pen.create_action(path)?);
319 Ok(())
320 }
321
322 fn fill(&mut self, brush: impl Brush, path: CFRetained<CGPath>) -> Result<()> {
323 self.actions.push(brush.create_action(path)?);
324 Ok(())
325 }
326
327 pub fn draw_path(&mut self, pen: impl Pen, path: &DrawingPath) -> Result<()> {
328 self.draw(pen, path.0.clone())
329 }
330
331 pub fn fill_path(&mut self, brush: impl Brush, path: &DrawingPath) -> Result<()> {
332 self.fill(brush, path.0.clone())
333 }
334
335 pub fn draw_arc(&mut self, pen: impl Pen, rect: Rect, start: f64, end: f64) -> Result<()> {
336 let path = path_arc(self.size, rect, start, end, false);
337 self.draw(pen, unsafe { CFRetained::cast_unchecked(path) })
338 }
339
340 pub fn draw_pie(&mut self, pen: impl Pen, rect: Rect, start: f64, end: f64) -> Result<()> {
341 let path = path_arc(self.size, rect, start, end, true);
342 self.draw(pen, unsafe { CFRetained::cast_unchecked(path) })
343 }
344
345 pub fn fill_pie(&mut self, brush: impl Brush, rect: Rect, start: f64, end: f64) -> Result<()> {
346 let path = path_arc(self.size, rect, start, end, true);
347 self.fill(brush, unsafe { CFRetained::cast_unchecked(path) })
348 }
349
350 pub fn draw_ellipse(&mut self, pen: impl Pen, rect: Rect) -> Result<()> {
351 let path = path_ellipse(self.size, rect);
352 self.draw(pen, path)
353 }
354
355 pub fn fill_ellipse(&mut self, brush: impl Brush, rect: Rect) -> Result<()> {
356 let path = path_ellipse(self.size, rect);
357 self.fill(brush, path)
358 }
359
360 pub fn draw_line(&mut self, pen: impl Pen, start: Point, end: Point) -> Result<()> {
361 let path = path_line(self.size, start, end);
362 self.draw(pen, unsafe { CFRetained::cast_unchecked(path) })
363 }
364
365 pub fn draw_rect(&mut self, pen: impl Pen, rect: Rect) -> Result<()> {
366 let path = path_rect(self.size, rect);
367 self.draw(pen, path)
368 }
369
370 pub fn fill_rect(&mut self, brush: impl Brush, rect: Rect) -> Result<()> {
371 let path = path_rect(self.size, rect);
372 self.fill(brush, path)
373 }
374
375 pub fn draw_round_rect(&mut self, pen: impl Pen, rect: Rect, round: Size) -> Result<()> {
376 let path = path_round_rect(self.size, rect, round);
377 self.draw(pen, path)
378 }
379
380 pub fn fill_round_rect(&mut self, brush: impl Brush, rect: Rect, round: Size) -> Result<()> {
381 let path = path_round_rect(self.size, rect, round);
382 self.fill(brush, path)
383 }
384
385 pub fn draw_str(
386 &mut self,
387 brush: impl Brush,
388 font: DrawingFont,
389 pos: Point,
390 text: &str,
391 ) -> Result<()> {
392 let color = brush.text_color()?;
393 let (framesetter, rect) = measure_str(font, &color, pos, text, self.size)?;
394 let rect = transform_rect(self.size, rect);
395 self.actions
396 .push(brush.create_text_action(framesetter, rect)?);
397 Ok(())
398 }
399
400 pub fn measure_str(&self, font: DrawingFont, text: &str) -> Result<Size> {
401 let color =
402 unsafe { CGColor::constant_color(Some(kCGColorWhite)).ok_or(Error::NullPointer) }?;
403 Ok(measure_str(font, &color, Point::zero(), text, self.size)?
404 .1
405 .size)
406 }
407
408 pub fn create_image(&self, image: DynamicImage) -> Result<DrawingImage> {
409 DrawingImage::new(image)
410 }
411
412 pub fn draw_image(
413 &mut self,
414 image_rep: &DrawingImage,
415 rect: Rect,
416 clip: Option<Rect>,
417 ) -> Result<()> {
418 let rect = transform_rect(self.size, rect);
419 let image_size = image_rep.size()?;
420 let clip = clip.map(|clip| transform_rect(image_size, clip));
421 self.actions
422 .push(DrawAction::Image(image_rep.clone(), rect, clip));
423 Ok(())
424 }
425
426 pub fn create_path_builder(&self, start: Point) -> Result<DrawingPathBuilder> {
427 Ok(DrawingPathBuilder::new(self.size, start))
428 }
429}
430
431pub struct DrawingPath(CFRetained<CGPath>);
432
433pub struct DrawingPathBuilder {
434 size: Size,
435 path: CFRetained<CGMutablePath>,
436}
437
438impl DrawingPathBuilder {
439 fn new(size: Size, start: Point) -> Self {
440 unsafe {
441 let path = CGMutablePath::new();
442 let p = transform_point(size, start);
443 CGMutablePath::move_to_point(Some(&path), null(), p.x, p.y);
444 Self { size, path }
445 }
446 }
447
448 pub fn add_line(&mut self, p: Point) -> Result<()> {
449 let p = transform_point(self.size, p);
450 unsafe {
451 CGMutablePath::add_line_to_point(Some(&self.path), null(), p.x, p.y);
452 }
453 Ok(())
454 }
455
456 pub fn add_arc(
457 &mut self,
458 center: Point,
459 radius: Size,
460 start: f64,
461 end: f64,
462 clockwise: bool,
463 ) -> Result<()> {
464 let startp = Point::new(
465 center.x + radius.width * start.cos(),
466 center.y + radius.height * start.sin(),
467 );
468
469 let rate = radius.height / radius.width;
470 let transform = CGAffineTransformMake(1.0, 0.0, 0.0, rate, 0.0, 0.0);
471
472 self.add_line(startp)?;
473 let center = transform_point(self.size, center);
474 unsafe {
475 CGMutablePath::add_arc(
476 Some(&self.path),
477 &transform,
478 center.x,
479 center.y / rate,
480 radius.width,
481 -start,
482 -end,
483 clockwise,
484 );
485 }
486 Ok(())
487 }
488
489 pub fn add_bezier(&mut self, p1: Point, p2: Point, p3: Point) -> Result<()> {
490 let p1 = transform_point(self.size, p1);
491 let p2 = transform_point(self.size, p2);
492 let p3 = transform_point(self.size, p3);
493 unsafe {
494 CGMutablePath::add_curve_to_point(
495 Some(&self.path),
496 null(),
497 p1.x,
498 p1.y,
499 p2.x,
500 p2.y,
501 p3.x,
502 p3.y,
503 );
504 }
505 Ok(())
506 }
507
508 pub fn build(self, close: bool) -> Result<DrawingPath> {
509 unsafe {
510 if close {
511 CGMutablePath::close_subpath(Some(&self.path));
512 }
513 Ok(DrawingPath(CFRetained::cast_unchecked(self.path)))
514 }
515 }
516}
517
518fn path_arc(s: Size, rect: Rect, start: f64, end: f64, pie: bool) -> CFRetained<CGMutablePath> {
519 let radius = rect.size / 2.0;
520 let centerp = Point::new(rect.origin.x + radius.width, rect.origin.y + radius.height);
521 let startp = Point::new(
522 centerp.x + radius.width * start.cos(),
523 centerp.y + radius.height * start.sin(),
524 );
525
526 let rate = radius.height / radius.width;
527 let transform = CGAffineTransformMake(1.0, 0.0, 0.0, rate, 0.0, 0.0);
528
529 unsafe {
530 let path = CGMutablePath::new();
531 let centerp = transform_point(s, centerp);
532 let startp = transform_point(s, startp);
533 if pie {
534 CGMutablePath::move_to_point(Some(&path), null(), centerp.x, centerp.y);
535 CGMutablePath::add_line_to_point(Some(&path), null(), startp.x, startp.y / rate);
536 } else {
537 CGMutablePath::move_to_point(Some(&path), null(), startp.x, startp.y);
538 }
539 CGMutablePath::add_arc(
540 Some(&path),
541 &transform,
542 centerp.x,
543 centerp.y / rate,
544 radius.width,
545 -start,
546 -end,
547 true,
548 );
549 if pie {
550 CGMutablePath::close_subpath(Some(&path));
551 }
552 path
553 }
554}
555
556fn path_ellipse(s: Size, rect: Rect) -> CFRetained<CGPath> {
557 let rect = transform_rect(s, rect);
558 unsafe { CGPath::with_ellipse_in_rect(rect, null()) }
559}
560
561fn path_line(s: Size, start: Point, end: Point) -> CFRetained<CGMutablePath> {
562 unsafe {
563 let path = CGMutablePath::new();
564 let p = transform_point(s, start);
565 CGMutablePath::move_to_point(Some(&path), null(), p.x, p.y);
566 let p = transform_point(s, end);
567 CGMutablePath::add_line_to_point(Some(&path), null(), p.x, p.y);
568 path
569 }
570}
571
572fn path_rect(s: Size, rect: Rect) -> CFRetained<CGPath> {
573 let rect = transform_rect(s, rect);
574 unsafe { CGPath::with_rect(rect, null()) }
575}
576
577fn path_round_rect(s: Size, rect: Rect, round: Size) -> CFRetained<CGPath> {
578 let rect = transform_rect(s, rect);
579 unsafe { CGPath::with_rounded_rect(rect, round.width, round.height, null()) }
580}
581
582fn measure_str(
583 font: DrawingFont,
584 color: &CGColor,
585 pos: Point,
586 text: &str,
587 bound: Size,
588) -> Result<(CFRetained<CTFramesetter>, Rect)> {
589 let astr = create_attr_str(&font, color, text)?;
590 let framesetter = unsafe { CTFramesetter::with_attributed_string(&astr) };
591 let size = from_cgsize(unsafe {
592 framesetter.suggest_frame_size_with_constraints(
593 CFRange::new(0, 0),
594 None,
595 NSSize::new(bound.width, bound.height + font.size),
596 null_mut(),
597 )
598 });
599 let mut x = pos.x;
600 let mut y = pos.y;
601 match font.halign {
602 HAlign::Center => x -= size.width / 2.0,
603 HAlign::Right => x -= size.width,
604 _ => {}
605 }
606 match font.valign {
607 VAlign::Center => y -= size.height / 2.0,
608 VAlign::Bottom => y -= size.height,
609 _ => {}
610 }
611 Ok((framesetter, Rect::new(Point::new(x, y), size)))
612}