Skip to main content

winio_ui_app_kit/widgets/
canvas.rs

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    // A buffer for actions, to avoid frequent allocations.
167    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}