// Copyright 2026 International Digital Economy Academy
//
// 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.
///|
/// Current drawing state.
pub struct DrawState {
mut path_open : Bool
mut path_start_x : Double
mut path_start_y : Double
mut current_x : Double
mut current_y : Double
} derive(Show, ToJson)
///|
pub fn DrawState::new() -> DrawState {
DrawState::{
path_open: false,
path_start_x: 0.0,
path_start_y: 0.0,
current_x: 0.0,
current_y: 0.0,
}
}
///|
/// Draw callback types.
pub type DrawMoveToFunc[T] = (T, DrawState, Double, Double) -> Unit
///|
pub type DrawLineToFunc[T] = (T, DrawState, Double, Double) -> Unit
///|
pub type DrawQuadraticToFunc[T] = (T, DrawState, Double, Double, Double, Double) -> Unit
///|
pub type DrawCubicToFunc[T] = (T, DrawState, Double, Double, Double, Double, Double, Double) -> Unit
///|
pub type DrawClosePathFunc[T] = (T, DrawState) -> Unit
fn[T] draw_move_to_nil(_data : T, _st : DrawState, _x : Double, _y : Double) -> Unit {}
fn[T] draw_line_to_nil(_data : T, _st : DrawState, _x : Double, _y : Double) -> Unit {}
fn[T] draw_cubic_to_nil(
_data : T,
_st : DrawState,
_x1 : Double,
_y1 : Double,
_x2 : Double,
_y2 : Double,
_x : Double,
_y : Double,
) -> Unit {
}
fn[T] draw_close_path_nil(_data : T, _st : DrawState) -> Unit {}
///|
/// Draw callbacks container.
pub struct DrawFuncs[T] {
mut move_to_cb : DrawMoveToFunc[T]
mut line_to_cb : DrawLineToFunc[T]
mut quadratic_to_cb : DrawQuadraticToFunc[T]?
mut cubic_to_cb : DrawCubicToFunc[T]
mut close_path_cb : DrawClosePathFunc[T]
mut immutable : Bool
}
///|
pub fn[T] DrawFuncs::new() -> DrawFuncs[T] {
DrawFuncs::{
move_to_cb: draw_move_to_nil,
line_to_cb: draw_line_to_nil,
quadratic_to_cb: None,
cubic_to_cb: draw_cubic_to_nil,
close_path_cb: draw_close_path_nil,
immutable: false,
}
}
///|
pub fn[T] DrawFuncs::empty() -> DrawFuncs[T] {
let funcs = DrawFuncs::new()
funcs.make_immutable()
funcs
}
///|
pub fn[T] DrawFuncs::make_immutable(self : DrawFuncs[T]) -> Unit {
self.immutable = true
}
///|
pub fn[T] DrawFuncs::is_immutable(self : DrawFuncs[T]) -> Bool {
self.immutable
}
///|
pub fn[T] DrawFuncs::set_move_to(self : DrawFuncs[T], func : DrawMoveToFunc[T]?) -> Unit {
if self.immutable {
return
}
match func {
Some(cb) => self.move_to_cb = cb
None => self.move_to_cb = draw_move_to_nil
}
}
///|
pub fn[T] DrawFuncs::set_line_to(self : DrawFuncs[T], func : DrawLineToFunc[T]?) -> Unit {
if self.immutable {
return
}
match func {
Some(cb) => self.line_to_cb = cb
None => self.line_to_cb = draw_line_to_nil
}
}
///|
pub fn[T] DrawFuncs::set_quadratic_to(
self : DrawFuncs[T],
func : DrawQuadraticToFunc[T]?,
) -> Unit {
if self.immutable {
return
}
self.quadratic_to_cb = func
}
///|
pub fn[T] DrawFuncs::set_cubic_to(self : DrawFuncs[T], func : DrawCubicToFunc[T]?) -> Unit {
if self.immutable {
return
}
match func {
Some(cb) => self.cubic_to_cb = cb
None => self.cubic_to_cb = draw_cubic_to_nil
}
}
///|
pub fn[T] DrawFuncs::set_close_path(self : DrawFuncs[T], func : DrawClosePathFunc[T]?) -> Unit {
if self.immutable {
return
}
match func {
Some(cb) => self.close_path_cb = cb
None => self.close_path_cb = draw_close_path_nil
}
}
///|
pub fn[T] DrawFuncs::emit_move_to(
self : DrawFuncs[T],
draw_data : T,
st : DrawState,
to_x : Double,
to_y : Double,
) -> Unit {
(self.move_to_cb)(draw_data, st, to_x, to_y)
}
///|
pub fn[T] DrawFuncs::emit_line_to(
self : DrawFuncs[T],
draw_data : T,
st : DrawState,
to_x : Double,
to_y : Double,
) -> Unit {
(self.line_to_cb)(draw_data, st, to_x, to_y)
}
///|
pub fn[T] DrawFuncs::emit_quadratic_to(
self : DrawFuncs[T],
draw_data : T,
st : DrawState,
control_x : Double,
control_y : Double,
to_x : Double,
to_y : Double,
) -> Unit {
match self.quadratic_to_cb {
Some(cb) => cb(draw_data, st, control_x, control_y, to_x, to_y)
None => {
let two_third = 2.0 / 3.0
let c1x = st.current_x + (control_x - st.current_x) * two_third
let c1y = st.current_y + (control_y - st.current_y) * two_third
let c2x = to_x + (control_x - to_x) * two_third
let c2y = to_y + (control_y - to_y) * two_third
self.emit_cubic_to(draw_data, st, c1x, c1y, c2x, c2y, to_x, to_y)
}
}
}
///|
pub fn[T] DrawFuncs::emit_cubic_to(
self : DrawFuncs[T],
draw_data : T,
st : DrawState,
control1_x : Double,
control1_y : Double,
control2_x : Double,
control2_y : Double,
to_x : Double,
to_y : Double,
) -> Unit {
(self.cubic_to_cb)(
draw_data,
st,
control1_x,
control1_y,
control2_x,
control2_y,
to_x,
to_y,
)
}
///|
pub fn[T] DrawFuncs::emit_close_path(self : DrawFuncs[T], draw_data : T, st : DrawState) -> Unit {
(self.close_path_cb)(draw_data, st)
}
///|
pub fn[T] DrawFuncs::move_to(
self : DrawFuncs[T],
draw_data : T,
st : DrawState,
to_x : Double,
to_y : Double,
) -> Unit {
if st.path_open {
self.close_path(draw_data, st)
}
st.current_x = to_x
st.current_y = to_y
}
///|
pub fn[T] DrawFuncs::line_to(
self : DrawFuncs[T],
draw_data : T,
st : DrawState,
to_x : Double,
to_y : Double,
) -> Unit {
if !st.path_open {
self.start_path(draw_data, st)
}
self.emit_line_to(draw_data, st, to_x, to_y)
st.current_x = to_x
st.current_y = to_y
}
///|
pub fn[T] DrawFuncs::quadratic_to(
self : DrawFuncs[T],
draw_data : T,
st : DrawState,
control_x : Double,
control_y : Double,
to_x : Double,
to_y : Double,
) -> Unit {
if !st.path_open {
self.start_path(draw_data, st)
}
self.emit_quadratic_to(draw_data, st, control_x, control_y, to_x, to_y)
st.current_x = to_x
st.current_y = to_y
}
///|
pub fn[T] DrawFuncs::cubic_to(
self : DrawFuncs[T],
draw_data : T,
st : DrawState,
control1_x : Double,
control1_y : Double,
control2_x : Double,
control2_y : Double,
to_x : Double,
to_y : Double,
) -> Unit {
if !st.path_open {
self.start_path(draw_data, st)
}
self.emit_cubic_to(
draw_data,
st,
control1_x,
control1_y,
control2_x,
control2_y,
to_x,
to_y,
)
st.current_x = to_x
st.current_y = to_y
}
///|
pub fn[T] DrawFuncs::close_path(self : DrawFuncs[T], draw_data : T, st : DrawState) -> Unit {
if st.path_open {
if st.path_start_x != st.current_x || st.path_start_y != st.current_y {
self.emit_line_to(draw_data, st, st.path_start_x, st.path_start_y)
}
self.emit_close_path(draw_data, st)
}
st.path_open = false
st.path_start_x = 0.0
st.path_start_y = 0.0
st.current_x = 0.0
st.current_y = 0.0
}
fn[T] DrawFuncs::start_path(self : DrawFuncs[T], draw_data : T, st : DrawState) -> Unit {
if st.path_open {
return
}
self.emit_move_to(draw_data, st, st.current_x, st.current_y)
st.path_open = true
st.path_start_x = st.current_x
st.path_start_y = st.current_y
}
///|
/// A draw session ties draw funcs and data to a mutable draw state.
pub struct DrawSession[T] {
funcs : DrawFuncs[T]
draw_data : T
st : DrawState
}
///|
pub fn[T] DrawSession::new(funcs : DrawFuncs[T], draw_data : T) -> DrawSession[T] {
DrawSession::{
funcs,
draw_data,
st: DrawState::new(),
}
}
///|
pub fn[T] DrawSession::move_to(self : DrawSession[T], to_x : Double, to_y : Double) -> Unit {
self.funcs.move_to(self.draw_data, self.st, to_x, to_y)
}
///|
pub fn[T] DrawSession::line_to(self : DrawSession[T], to_x : Double, to_y : Double) -> Unit {
self.funcs.line_to(self.draw_data, self.st, to_x, to_y)
}
///|
pub fn[T] DrawSession::quadratic_to(
self : DrawSession[T],
control_x : Double,
control_y : Double,
to_x : Double,
to_y : Double,
) -> Unit {
self.funcs.quadratic_to(self.draw_data, self.st, control_x, control_y, to_x, to_y)
}
///|
pub fn[T] DrawSession::cubic_to(
self : DrawSession[T],
control1_x : Double,
control1_y : Double,
control2_x : Double,
control2_y : Double,
to_x : Double,
to_y : Double,
) -> Unit {
self.funcs.cubic_to(
self.draw_data,
self.st,
control1_x,
control1_y,
control2_x,
control2_y,
to_x,
to_y,
)
}
///|
pub fn[T] DrawSession::close_path(self : DrawSession[T]) -> Unit {
self.funcs.close_path(self.draw_data, self.st)
}
///|
/// Bounding box builder for draw callbacks.
pub struct DrawExtents {
mut xmin : Double
mut ymin : Double
mut xmax : Double
mut ymax : Double
} derive(Show, ToJson)
///|
pub fn DrawExtents::new() -> DrawExtents {
DrawExtents::{ xmin: 0.0, ymin: 0.0, xmax: -1.0, ymax: -1.0 }
}
///|
pub fn DrawExtents::is_empty(self : DrawExtents) -> Bool {
self.xmin >= self.xmax || self.ymin >= self.ymax
}
///|
pub fn DrawExtents::is_void(self : DrawExtents) -> Bool {
self.xmin > self.xmax
}
///|
pub fn DrawExtents::add_point(self : DrawExtents, x : Double, y : Double) -> Unit {
if self.is_void() {
self.xmin = x
self.xmax = x
self.ymin = y
self.ymax = y
return
}
if x < self.xmin {
self.xmin = x
}
if y < self.ymin {
self.ymin = y
}
if x > self.xmax {
self.xmax = x
}
if y > self.ymax {
self.ymax = y
}
}
fn draw_extents_move_to(data : DrawExtents, _st : DrawState, to_x : Double, to_y : Double) -> Unit {
data.add_point(to_x, to_y)
}
fn draw_extents_line_to(data : DrawExtents, _st : DrawState, to_x : Double, to_y : Double) -> Unit {
data.add_point(to_x, to_y)
}
fn draw_extents_quadratic_to(
data : DrawExtents,
_st : DrawState,
control_x : Double,
control_y : Double,
to_x : Double,
to_y : Double,
) -> Unit {
data.add_point(control_x, control_y)
data.add_point(to_x, to_y)
}
fn draw_extents_cubic_to(
data : DrawExtents,
_st : DrawState,
control1_x : Double,
control1_y : Double,
control2_x : Double,
control2_y : Double,
to_x : Double,
to_y : Double,
) -> Unit {
data.add_point(control1_x, control1_y)
data.add_point(control2_x, control2_y)
data.add_point(to_x, to_y)
}
///|
pub fn draw_extents_get_funcs() -> DrawFuncs[DrawExtents] {
let funcs : DrawFuncs[DrawExtents] = DrawFuncs::new()
funcs.set_move_to(Some(draw_extents_move_to))
funcs.set_line_to(Some(draw_extents_line_to))
funcs.set_quadratic_to(Some(draw_extents_quadratic_to))
funcs.set_cubic_to(Some(draw_extents_cubic_to))
funcs.make_immutable()
funcs
}