piki-gui 0.7.1

FLTK-based GUI for Piki personal wiki application
Documentation
use crate::nonprintable::printable;
use fltk::{draw as fltk_draw, enums::*, prelude::*};
use rutle::render_context::{CaretLean, FontStyle, FontType, RenderContext};

/// FLTK implementation of rutle's [`RenderContext`].
pub struct FltkDrawContext {
    has_focus: bool,
    is_active: bool,
}

impl FltkDrawContext {
    pub fn new(has_focus: bool, is_active: bool) -> Self {
        FltkDrawContext {
            has_focus,
            is_active,
        }
    }

    pub fn from_widget_ptr<T: WidgetExt>(widget: &T) -> Self {
        let has_focus = fltk::app::focus().map(|f| f.as_base_widget()).as_ref()
            == Some(&widget.as_base_widget());

        Self::new(has_focus, widget.active())
    }
}

impl FltkDrawContext {
    fn inner_set_font(&self, font: FontType, style: FontStyle, size: u8) {
        fltk_draw::set_font(
            match font {
                FontType::Content => match style {
                    FontStyle::Regular => Font::Helvetica,
                    FontStyle::Bold => Font::HelveticaBold,
                    FontStyle::Italic => Font::HelveticaItalic,
                    FontStyle::BoldItalic => Font::HelveticaBoldItalic,
                },
                FontType::Code => match style {
                    FontStyle::Regular => Font::Courier,
                    FontStyle::Bold => Font::CourierBold,
                    FontStyle::Italic => Font::CourierItalic,
                    FontStyle::BoldItalic => Font::CourierBoldItalic,
                },
                FontType::Heading => match style {
                    FontStyle::Regular => Font::Helvetica,
                    FontStyle::Bold => Font::HelveticaBold,
                    FontStyle::Italic => Font::HelveticaItalic,
                    FontStyle::BoldItalic => Font::HelveticaBoldItalic,
                },
            },
            size as i32,
        );
    }
}

impl RenderContext for FltkDrawContext {
    fn set_color(&mut self, color: u32) {
        let r = ((color >> 24) & 0xFF) as u8;
        let g = ((color >> 16) & 0xFF) as u8;
        let b = ((color >> 8) & 0xFF) as u8;
        fltk_draw::set_draw_color(Color::from_rgb(r, g, b));
    }

    fn set_font(&mut self, font: FontType, style: FontStyle, size: u8) {
        self.inner_set_font(font, style, size);
    }

    fn draw_text(&mut self, text: &str, x: i32, y: i32) {
        fltk_draw::draw_text(&printable(text), x, y);
    }

    fn draw_rect_filled(&mut self, x: i32, y: i32, w: i32, h: i32) {
        fltk_draw::draw_rectf(x, y, w, h);
    }

    fn draw_line(&mut self, x1: i32, y1: i32, x2: i32, y2: i32) {
        fltk_draw::draw_line(x1, y1, x2, y2);
    }

    /// Piki's caret design: a uniform-width bracket leaning toward the affinity
    /// side — the 2px bar plus two arms (the bar's end edges extruded along the
    /// lean) — filled as one connected polygon, so there are no seams where the
    /// arms meet the bar. (rutle's default draws a plainer foot tick.)
    fn draw_caret(&mut self, x: i32, y: i32, height: i32, lean: CaretLean) {
        let dir = match lean {
            CaretLean::None => {
                fltk_draw::draw_rectf(x, y, 2, height);
                return;
            }
            CaretLean::Left => -1,
            CaretLean::Right => 1,
        };
        let w = 2; // bar / arm width
        let reach = dir * 4; // horizontal arm extent, toward the lean
        let rise = 2; // vertical arm flare
        let edge = if dir > 0 { x + w } else { x }; // bar edge the arms sprout from
        let far = edge - dir * w; // opposite bar edge
        let top = y;
        let bottom = y + height - 1;
        let tip_edge = edge + reach;
        let tip_far = far + reach;
        let pts = [
            (tip_far, top - rise),     // head tip, outer
            (tip_edge, top - rise),    // head tip, inner (flat tip)
            (edge, top),               // arm meets spine (top)
            (edge, bottom),            // down the spine's near edge
            (tip_edge, bottom + rise), // foot tip, inner
            (tip_far, bottom + rise),  // foot tip, outer (flat tip)
            (far, bottom),             // spine far edge (bottom)
            (far, top),                // up the spine's far edge
        ];
        fltk_draw::begin_complex_polygon();
        for (vx, vy) in pts {
            fltk_draw::vertex(vx as f64, vy as f64);
        }
        fltk_draw::end_complex_polygon();
    }

    fn text_width(&mut self, text: &str, font: FontType, style: FontStyle, size: u8) -> f64 {
        self.set_font(font, style, size);
        fltk_draw::width(&printable(text))
    }

    fn text_height(&self, font: FontType, style: FontStyle, size: u8) -> i32 {
        self.inner_set_font(font, style, size);
        fltk_draw::height()
    }

    fn text_descent(&self, font: FontType, style: FontStyle, size: u8) -> i32 {
        self.inner_set_font(font, style, size);
        fltk_draw::descent()
    }

    fn push_clip(&mut self, x: i32, y: i32, w: i32, h: i32) {
        fltk_draw::push_clip(x, y, w, h);
    }

    fn pop_clip(&mut self) {
        fltk_draw::pop_clip();
    }

    fn color_average(&self, c1: u32, c2: u32, weight: f32) -> u32 {
        let r1 = ((c1 >> 24) & 0xFF) as f32;
        let g1 = ((c1 >> 16) & 0xFF) as f32;
        let b1 = ((c1 >> 8) & 0xFF) as f32;

        let r2 = ((c2 >> 24) & 0xFF) as f32;
        let g2 = ((c2 >> 16) & 0xFF) as f32;
        let b2 = ((c2 >> 8) & 0xFF) as f32;

        let r = (r1 * (1.0 - weight) + r2 * weight) as u32;
        let g = (g1 * (1.0 - weight) + g2 * weight) as u32;
        let b = (b1 * (1.0 - weight) + b2 * weight) as u32;

        (r << 24) | (g << 16) | (b << 8) | 0xFF
    }

    fn color_contrast(&self, _fg: u32, bg: u32) -> u32 {
        let r = ((bg >> 24) & 0xFF) as f32;
        let g = ((bg >> 16) & 0xFF) as f32;
        let b = ((bg >> 8) & 0xFF) as f32;

        let brightness = (r * 0.299 + g * 0.587 + b * 0.114) / 255.0;

        if brightness > 0.5 {
            0x000000FF // Black
        } else {
            0xFFFFFFFF // White
        }
    }

    fn color_inactive(&self, c: u32) -> u32 {
        let r = ((c >> 24) & 0xFF) as f32;
        let g = ((c >> 16) & 0xFF) as f32;
        let b = ((c >> 8) & 0xFF) as f32;

        let gray = (r + g + b) / 3.0;
        let r = (r * 0.5 + gray * 0.5) as u32;
        let g = (g * 0.5 + gray * 0.5) as u32;
        let b = (b * 0.5 + gray * 0.5) as u32;

        (r << 24) | (g << 16) | (b << 8) | 0xFF
    }

    fn has_focus(&self) -> bool {
        self.has_focus
    }

    fn is_active(&self) -> bool {
        self.is_active
    }
}