use crate::basics::{
is_stop, VertexSource, PATH_CMD_LINE_TO, PATH_CMD_MOVE_TO, PATH_CMD_STOP,
};
use crate::bspline::Bspline;
use crate::color::Rgba8;
use crate::conv_stroke::ConvStroke;
use crate::ellipse::Ellipse;
use crate::gsv_text::GsvText;
use crate::math_stroke::{LineCap, LineJoin};
use crate::path_storage::PathStorage;
use crate::pixfmt_rgba::PixfmtRgba32;
use crate::rasterizer_scanline_aa::RasterizerScanlineAa;
use crate::renderer_base::RendererBase;
use crate::renderer_scanline::render_scanlines_aa_solid;
use crate::scanline_u::ScanlineU8;
pub fn render_ctrl(
ras: &mut RasterizerScanlineAa,
sl: &mut ScanlineU8,
ren: &mut RendererBase<PixfmtRgba32>,
ctrl: &mut dyn Ctrl,
) {
for i in 0..ctrl.num_paths() {
ras.reset();
ras.add_path(ctrl, i);
render_scanlines_aa_solid(ras, sl, ren, &ctrl.color(i));
}
}
pub trait Ctrl: VertexSource {
fn num_paths(&self) -> u32;
fn color(&self, path_id: u32) -> Rgba8;
}
fn sprintf_format(fmt: &str, value: f64) -> String {
let mut result = String::with_capacity(fmt.len() + 16);
let bytes = fmt.as_bytes();
let mut i = 0;
let mut formatted = false;
while i < bytes.len() {
if !formatted && bytes[i] == b'%' {
let start = i;
i += 1; if i < bytes.len() && bytes[i] == b'%' {
result.push('%');
i += 1;
continue;
}
while i < bytes.len() && matches!(bytes[i], b'-' | b'+' | b'0' | b' ') {
i += 1;
}
while i < bytes.len() && bytes[i].is_ascii_digit() {
i += 1;
}
let mut precision: Option<usize> = None;
if i < bytes.len() && bytes[i] == b'.' {
i += 1;
let prec_start = i;
while i < bytes.len() && bytes[i].is_ascii_digit() {
i += 1;
}
if i > prec_start {
precision = fmt[prec_start..i].parse().ok();
} else {
precision = Some(0);
}
}
if i < bytes.len() {
match bytes[i] {
b'f' | b'F' => {
let p = precision.unwrap_or(6);
result.push_str(&format!("{:.prec$}", value, prec = p));
i += 1;
formatted = true;
}
b'd' | b'i' => {
result.push_str(&format!("{}", value as i64));
i += 1;
formatted = true;
}
b'e' | b'E' => {
let p = precision.unwrap_or(6);
result.push_str(&format!("{:.prec$e}", value, prec = p));
i += 1;
formatted = true;
}
b'g' | b'G' => {
let p = precision.unwrap_or(6);
let s = format!("{:.prec$}", value, prec = p);
let e = format!("{:.prec$e}", value, prec = p);
result.push_str(if s.len() <= e.len() { &s } else { &e });
i += 1;
formatted = true;
}
_ => {
result.push_str(&fmt[start..=i]);
i += 1;
}
}
} else {
result.push('%');
}
} else {
result.push(bytes[i] as char);
i += 1;
}
}
result
}
pub struct SliderCtrl {
x1: f64,
y1: f64,
x2: f64,
y2: f64,
xs1: f64,
ys1: f64,
xs2: f64,
ys2: f64,
value: f64,
preview_value: f64,
min: f64,
max: f64,
label: String,
border_width: f64,
border_extra: f64,
text_thickness: f64,
num_steps: u32,
descending: bool,
colors: [Rgba8; 6],
vertices: Vec<(f64, f64, u32)>,
vertex_idx: usize,
}
impl SliderCtrl {
pub fn new(x1: f64, y1: f64, x2: f64, y2: f64) -> Self {
let border_extra = (y2 - y1) / 2.0;
let mut s = Self {
x1,
y1,
x2,
y2,
xs1: 0.0,
ys1: 0.0,
xs2: 0.0,
ys2: 0.0,
value: 0.5,
preview_value: 0.5,
min: 0.0,
max: 1.0,
label: String::new(),
border_width: 1.0,
border_extra,
text_thickness: 1.0,
num_steps: 0,
descending: false,
colors: [
Rgba8::new(255, 230, 204, 255), Rgba8::new(179, 153, 153, 255), Rgba8::new(0, 0, 0, 255), Rgba8::new(153, 102, 102, 102), Rgba8::new(204, 0, 0, 153), Rgba8::new(0, 0, 0, 255), ],
vertices: Vec::new(),
vertex_idx: 0,
};
s.calc_box();
s
}
fn calc_box(&mut self) {
self.xs1 = self.x1 + self.border_width;
self.ys1 = self.y1 + self.border_width;
self.xs2 = self.x2 - self.border_width;
self.ys2 = self.y2 - self.border_width;
}
pub fn label(&mut self, fmt: &str) {
self.label = fmt.to_string();
}
pub fn range(&mut self, min: f64, max: f64) {
self.min = min;
self.max = max;
}
pub fn value(&self) -> f64 {
self.value * (self.max - self.min) + self.min
}
pub fn set_value(&mut self, v: f64) {
self.preview_value = ((v - self.min) / (self.max - self.min)).clamp(0.0, 1.0);
self.normalize_value(true);
}
pub fn num_steps(&mut self, n: u32) {
self.num_steps = n;
}
pub fn set_descending(&mut self, d: bool) {
self.descending = d;
}
pub fn border_width(&mut self, t: f64, extra: f64) {
self.border_width = t;
self.border_extra = extra;
self.calc_box();
}
pub fn text_thickness(&mut self, t: f64) {
self.text_thickness = t;
}
fn normalize_value(&mut self, preview_value_flag: bool) {
if self.num_steps > 0 {
let step = (self.preview_value * self.num_steps as f64 + 0.5) as i32;
self.value = step as f64 / self.num_steps as f64;
} else {
self.value = self.preview_value;
}
if preview_value_flag {
self.preview_value = self.value;
}
}
fn calc_background(&mut self) {
let (x1, y1, x2, y2) = (self.x1, self.y1, self.x2, self.y2);
let be = self.border_extra;
self.vertices.clear();
self.vertices.push((x1 - be, y1 - be, PATH_CMD_MOVE_TO));
self.vertices.push((x2 + be, y1 - be, PATH_CMD_LINE_TO));
self.vertices.push((x2 + be, y2 + be, PATH_CMD_LINE_TO));
self.vertices.push((x1 - be, y2 + be, PATH_CMD_LINE_TO));
}
fn calc_triangle(&mut self) {
self.vertices.clear();
if self.descending {
self.vertices.push((self.x1, self.y1, PATH_CMD_MOVE_TO));
self.vertices.push((self.x2, self.y1, PATH_CMD_LINE_TO));
self.vertices.push((self.x1, self.y2, PATH_CMD_LINE_TO));
self.vertices.push((self.x1, self.y1, PATH_CMD_LINE_TO));
} else {
self.vertices.push((self.x1, self.y1, PATH_CMD_MOVE_TO));
self.vertices.push((self.x2, self.y1, PATH_CMD_LINE_TO));
self.vertices.push((self.x2, self.y2, PATH_CMD_LINE_TO));
self.vertices.push((self.x1, self.y1, PATH_CMD_LINE_TO));
}
}
fn calc_text(&mut self) {
self.vertices.clear();
let text = if self.label.contains('%') {
sprintf_format(&self.label, self.value())
} else if self.label.is_empty() {
return;
} else {
self.label.clone()
};
let text_height = self.y2 - self.y1;
let mut txt = GsvText::new();
txt.start_point(self.x1, self.y1);
txt.size(text_height * 1.2, text_height);
txt.text(&text);
let mut stroke = ConvStroke::new(txt);
stroke.set_width(self.text_thickness);
stroke.set_line_join(LineJoin::Round);
stroke.set_line_cap(LineCap::Round);
stroke.rewind(0);
loop {
let (mut x, mut y) = (0.0, 0.0);
let cmd = stroke.vertex(&mut x, &mut y);
if is_stop(cmd) {
break;
}
self.vertices.push((x, y, cmd));
}
}
fn calc_pointer_preview(&mut self) {
self.vertices.clear();
let cx = self.xs1 + (self.xs2 - self.xs1) * self.preview_value;
let cy = (self.ys1 + self.ys2) / 2.0;
let r = self.y2 - self.y1;
let mut ell = Ellipse::new(cx, cy, r, r, 32, false);
ell.rewind(0);
loop {
let (mut x, mut y) = (0.0, 0.0);
let cmd = ell.vertex(&mut x, &mut y);
if is_stop(cmd) {
break;
}
self.vertices.push((x, y, cmd));
}
}
fn calc_pointer(&mut self) {
self.normalize_value(false);
self.vertices.clear();
let cx = self.xs1 + (self.xs2 - self.xs1) * self.value;
let cy = (self.ys1 + self.ys2) / 2.0;
let r = self.y2 - self.y1;
let mut ell = Ellipse::new(cx, cy, r, r, 32, false);
ell.rewind(0);
loop {
let (mut x, mut y) = (0.0, 0.0);
let cmd = ell.vertex(&mut x, &mut y);
if is_stop(cmd) {
break;
}
self.vertices.push((x, y, cmd));
}
}
fn calc_steps(&mut self) {
self.vertices.clear();
if self.num_steps == 0 {
return;
}
let mut d = (self.xs2 - self.xs1) / self.num_steps as f64;
if d > 0.004 {
d = 0.004;
}
for i in 0..=self.num_steps {
let x = self.xs1 + (self.xs2 - self.xs1) * i as f64 / self.num_steps as f64;
self.vertices.push((x, self.y1, PATH_CMD_MOVE_TO));
self.vertices
.push((x - d * (self.x2 - self.x1), self.y1 - self.border_extra, PATH_CMD_LINE_TO));
self.vertices
.push((x + d * (self.x2 - self.x1), self.y1 - self.border_extra, PATH_CMD_LINE_TO));
}
}
}
impl Ctrl for SliderCtrl {
fn num_paths(&self) -> u32 {
6
}
fn color(&self, path_id: u32) -> Rgba8 {
self.colors[path_id.min(5) as usize]
}
}
impl VertexSource for SliderCtrl {
fn rewind(&mut self, path_id: u32) {
self.vertex_idx = 0;
match path_id {
0 => self.calc_background(),
1 => self.calc_triangle(),
2 => self.calc_text(),
3 => self.calc_pointer_preview(),
4 => self.calc_pointer(),
5 => self.calc_steps(),
_ => {
self.vertices.clear();
}
}
}
fn vertex(&mut self, x: &mut f64, y: &mut f64) -> u32 {
if self.vertex_idx < self.vertices.len() {
let (vx, vy, cmd) = self.vertices[self.vertex_idx];
*x = vx;
*y = vy;
self.vertex_idx += 1;
cmd
} else {
PATH_CMD_STOP
}
}
}
pub struct ScaleCtrl {
x1: f64,
y1: f64,
x2: f64,
y2: f64,
xs1: f64,
ys1: f64,
xs2: f64,
ys2: f64,
border_width: f64,
border_extra: f64,
value1: f64,
value2: f64,
min_delta: f64,
colors: [Rgba8; 5],
vertices: Vec<(f64, f64, u32)>,
vertex_idx: usize,
}
impl ScaleCtrl {
pub fn new(x1: f64, y1: f64, x2: f64, y2: f64) -> Self {
let mut s = Self {
x1,
y1,
x2,
y2,
xs1: 0.0,
ys1: 0.0,
xs2: 0.0,
ys2: 0.0,
border_width: 1.0,
border_extra: (y2 - y1) * 0.5,
value1: 0.3,
value2: 0.7,
min_delta: 0.01,
colors: [
Rgba8::new(255, 230, 204, 255), Rgba8::new(0, 0, 0, 255), Rgba8::new(180, 120, 120, 180), Rgba8::new(204, 0, 0, 180), Rgba8::new(204, 0, 0, 180), ],
vertices: Vec::new(),
vertex_idx: 0,
};
s.calc_box();
s
}
fn calc_box(&mut self) {
self.xs1 = self.x1 + self.border_width;
self.ys1 = self.y1 + self.border_width;
self.xs2 = self.x2 - self.border_width;
self.ys2 = self.y2 - self.border_width;
}
pub fn set_min_delta(&mut self, d: f64) {
self.min_delta = d.clamp(0.0, 1.0);
self.set_value1(self.value1);
self.set_value2(self.value2);
}
pub fn value1(&self) -> f64 {
self.value1
}
pub fn value2(&self) -> f64 {
self.value2
}
pub fn set_value1(&mut self, v: f64) {
let max_v1 = (self.value2 - self.min_delta).clamp(0.0, 1.0);
self.value1 = v.clamp(0.0, max_v1);
}
pub fn set_value2(&mut self, v: f64) {
let min_v2 = (self.value1 + self.min_delta).clamp(0.0, 1.0);
self.value2 = v.clamp(min_v2, 1.0);
}
fn calc_background(&mut self) {
self.vertices.clear();
let be = self.border_extra;
self.vertices
.push((self.x1 - be, self.y1 - be, PATH_CMD_MOVE_TO));
self.vertices
.push((self.x2 + be, self.y1 - be, PATH_CMD_LINE_TO));
self.vertices
.push((self.x2 + be, self.y2 + be, PATH_CMD_LINE_TO));
self.vertices
.push((self.x1 - be, self.y2 + be, PATH_CMD_LINE_TO));
}
fn calc_border(&mut self) {
self.vertices.clear();
self.vertices.push((self.x1, self.y1, PATH_CMD_MOVE_TO));
self.vertices.push((self.x2, self.y1, PATH_CMD_LINE_TO));
self.vertices.push((self.x2, self.y2, PATH_CMD_LINE_TO));
self.vertices.push((self.x1, self.y2, PATH_CMD_LINE_TO));
self.vertices.push((
self.x1 + self.border_width,
self.y1 + self.border_width,
PATH_CMD_MOVE_TO,
));
self.vertices.push((
self.x1 + self.border_width,
self.y2 - self.border_width,
PATH_CMD_LINE_TO,
));
self.vertices.push((
self.x2 - self.border_width,
self.y2 - self.border_width,
PATH_CMD_LINE_TO,
));
self.vertices.push((
self.x2 - self.border_width,
self.y1 + self.border_width,
PATH_CMD_LINE_TO,
));
}
fn calc_selected_range(&mut self) {
self.vertices.clear();
let x1 = self.xs1 + (self.xs2 - self.xs1) * self.value1;
let x2 = self.xs1 + (self.xs2 - self.xs1) * self.value2;
self.vertices.push((x1, self.ys1, PATH_CMD_MOVE_TO));
self.vertices.push((x2, self.ys1, PATH_CMD_LINE_TO));
self.vertices.push((x2, self.ys2, PATH_CMD_LINE_TO));
self.vertices.push((x1, self.ys2, PATH_CMD_LINE_TO));
}
fn calc_handle(&mut self, v: f64) {
self.vertices.clear();
let cx = self.xs1 + (self.xs2 - self.xs1) * v;
let cy = (self.ys1 + self.ys2) * 0.5;
let r = (self.y2 - self.y1).max(1.0);
let mut ell = Ellipse::new(cx, cy, r, r, 32, false);
ell.rewind(0);
loop {
let (mut x, mut y) = (0.0, 0.0);
let cmd = ell.vertex(&mut x, &mut y);
if is_stop(cmd) {
break;
}
self.vertices.push((x, y, cmd));
}
}
}
impl Ctrl for ScaleCtrl {
fn num_paths(&self) -> u32 {
5
}
fn color(&self, path_id: u32) -> Rgba8 {
self.colors[path_id.min(4) as usize]
}
}
impl VertexSource for ScaleCtrl {
fn rewind(&mut self, path_id: u32) {
self.vertex_idx = 0;
match path_id {
0 => self.calc_background(),
1 => self.calc_border(),
2 => self.calc_selected_range(),
3 => self.calc_handle(self.value1),
4 => self.calc_handle(self.value2),
_ => self.vertices.clear(),
}
}
fn vertex(&mut self, x: &mut f64, y: &mut f64) -> u32 {
if self.vertex_idx < self.vertices.len() {
let (vx, vy, cmd) = self.vertices[self.vertex_idx];
*x = vx;
*y = vy;
self.vertex_idx += 1;
cmd
} else {
PATH_CMD_STOP
}
}
}
pub struct CboxCtrl {
x1: f64,
y1: f64,
x2: f64,
y2: f64,
text_thickness: f64,
text_height: f64,
text_width: f64,
label: String,
status: bool,
colors: [Rgba8; 3],
vertices: Vec<(f64, f64, u32)>,
vertex_idx: usize,
}
impl CboxCtrl {
pub fn new(x: f64, y: f64, label: &str) -> Self {
let text_height = 9.0;
Self {
x1: x,
y1: y,
x2: x + text_height * 1.5,
y2: y + text_height * 1.5,
text_thickness: 1.5,
text_height,
text_width: 0.0,
label: label.to_string(),
status: false,
colors: [
Rgba8::new(0, 0, 0, 255), Rgba8::new(0, 0, 0, 255), Rgba8::new(102, 0, 0, 255), ],
vertices: Vec::new(),
vertex_idx: 0,
}
}
pub fn set_status(&mut self, s: bool) {
self.status = s;
}
pub fn status(&self) -> bool {
self.status
}
pub fn text_size(&mut self, h: f64, w: f64) {
self.text_height = h;
self.text_width = w;
}
fn calc_border(&mut self) {
self.vertices.clear();
let t = self.text_thickness;
self.vertices.push((self.x1, self.y1, PATH_CMD_MOVE_TO));
self.vertices.push((self.x2, self.y1, PATH_CMD_LINE_TO));
self.vertices.push((self.x2, self.y2, PATH_CMD_LINE_TO));
self.vertices.push((self.x1, self.y2, PATH_CMD_LINE_TO));
self.vertices.push((self.x1 + t, self.y1 + t, PATH_CMD_MOVE_TO));
self.vertices.push((self.x1 + t, self.y2 - t, PATH_CMD_LINE_TO));
self.vertices.push((self.x2 - t, self.y2 - t, PATH_CMD_LINE_TO));
self.vertices.push((self.x2 - t, self.y1 + t, PATH_CMD_LINE_TO));
}
fn calc_text(&mut self) {
self.vertices.clear();
let mut txt = GsvText::new();
txt.start_point(
self.x1 + self.text_height * 2.0,
self.y1 + self.text_height / 5.0,
);
txt.size(self.text_height, self.text_width);
txt.text(&self.label);
let mut stroke = ConvStroke::new(txt);
stroke.set_width(self.text_thickness);
stroke.set_line_join(LineJoin::Round);
stroke.set_line_cap(LineCap::Round);
stroke.rewind(0);
loop {
let (mut x, mut y) = (0.0, 0.0);
let cmd = stroke.vertex(&mut x, &mut y);
if is_stop(cmd) {
break;
}
self.vertices.push((x, y, cmd));
}
}
fn calc_checkmark(&mut self) {
self.vertices.clear();
if !self.status {
return;
}
let d2 = (self.y2 - self.y1) / 2.0;
let t = self.text_thickness * 1.5;
self.vertices.push((self.x1 + self.text_thickness, self.y1 + self.text_thickness, PATH_CMD_MOVE_TO));
self.vertices.push((self.x1 + d2, self.y1 + d2 - t, PATH_CMD_LINE_TO));
self.vertices.push((self.x2 - self.text_thickness, self.y1 + self.text_thickness, PATH_CMD_LINE_TO));
self.vertices.push((self.x1 + d2 + t, self.y1 + d2, PATH_CMD_LINE_TO));
self.vertices.push((self.x2 - self.text_thickness, self.y2 - self.text_thickness, PATH_CMD_LINE_TO));
self.vertices.push((self.x1 + d2, self.y1 + d2 + t, PATH_CMD_LINE_TO));
self.vertices.push((self.x1 + self.text_thickness, self.y2 - self.text_thickness, PATH_CMD_LINE_TO));
self.vertices.push((self.x1 + d2 - t, self.y1 + d2, PATH_CMD_LINE_TO));
}
}
impl Ctrl for CboxCtrl {
fn num_paths(&self) -> u32 {
3
}
fn color(&self, path_id: u32) -> Rgba8 {
self.colors[path_id.min(2) as usize]
}
}
impl VertexSource for CboxCtrl {
fn rewind(&mut self, path_id: u32) {
self.vertex_idx = 0;
match path_id {
0 => self.calc_border(),
1 => self.calc_text(),
2 => self.calc_checkmark(),
_ => {
self.vertices.clear();
}
}
}
fn vertex(&mut self, x: &mut f64, y: &mut f64) -> u32 {
if self.vertex_idx < self.vertices.len() {
let (vx, vy, cmd) = self.vertices[self.vertex_idx];
*x = vx;
*y = vy;
self.vertex_idx += 1;
cmd
} else {
PATH_CMD_STOP
}
}
}
pub struct RboxCtrl {
x1: f64,
y1: f64,
x2: f64,
y2: f64,
xs1: f64,
ys1: f64,
xs2: f64,
ys2: f64,
border_width: f64,
border_extra: f64,
text_thickness: f64,
text_height: f64,
text_width: f64,
items: Vec<String>,
cur_item: i32,
dy: f64,
colors: [Rgba8; 5],
vertices: Vec<(f64, f64, u32)>,
vertex_idx: usize,
}
impl RboxCtrl {
pub fn new(x1: f64, y1: f64, x2: f64, y2: f64) -> Self {
let mut r = Self {
x1,
y1,
x2,
y2,
xs1: 0.0,
ys1: 0.0,
xs2: 0.0,
ys2: 0.0,
border_width: 1.0,
border_extra: 0.0,
text_thickness: 1.5,
text_height: 9.0,
text_width: 0.0,
items: Vec::new(),
cur_item: -1,
dy: 18.0,
colors: [
Rgba8::new(255, 255, 230, 255), Rgba8::new(0, 0, 0, 255), Rgba8::new(0, 0, 0, 255), Rgba8::new(0, 0, 0, 255), Rgba8::new(102, 0, 0, 255), ],
vertices: Vec::new(),
vertex_idx: 0,
};
r.calc_rbox();
r
}
fn calc_rbox(&mut self) {
self.xs1 = self.x1 + self.border_width;
self.ys1 = self.y1 + self.border_width;
self.xs2 = self.x2 - self.border_width;
self.ys2 = self.y2 - self.border_width;
}
pub fn add_item(&mut self, text: &str) {
self.items.push(text.to_string());
}
pub fn cur_item(&self) -> i32 {
self.cur_item
}
pub fn set_cur_item(&mut self, i: i32) {
self.cur_item = i;
}
pub fn text_size(&mut self, h: f64, w: f64) {
self.text_height = h;
self.text_width = w;
}
pub fn border_width(&mut self, t: f64, extra: f64) {
self.border_width = t;
self.border_extra = extra;
self.calc_rbox();
}
pub fn text_thickness(&mut self, t: f64) {
self.text_thickness = t;
}
pub fn background_color(&mut self, c: Rgba8) {
self.colors[0] = c;
}
fn calc_background(&mut self) {
self.vertices.clear();
let be = self.border_extra;
self.vertices.push((self.x1 - be, self.y1 - be, PATH_CMD_MOVE_TO));
self.vertices.push((self.x2 + be, self.y1 - be, PATH_CMD_LINE_TO));
self.vertices.push((self.x2 + be, self.y2 + be, PATH_CMD_LINE_TO));
self.vertices.push((self.x1 - be, self.y2 + be, PATH_CMD_LINE_TO));
}
fn calc_border(&mut self) {
self.vertices.clear();
let bw = self.border_width;
self.vertices.push((self.x1, self.y1, PATH_CMD_MOVE_TO));
self.vertices.push((self.x2, self.y1, PATH_CMD_LINE_TO));
self.vertices.push((self.x2, self.y2, PATH_CMD_LINE_TO));
self.vertices.push((self.x1, self.y2, PATH_CMD_LINE_TO));
self.vertices.push((self.x1 + bw, self.y1 + bw, PATH_CMD_MOVE_TO));
self.vertices.push((self.x1 + bw, self.y2 - bw, PATH_CMD_LINE_TO));
self.vertices.push((self.x2 - bw, self.y2 - bw, PATH_CMD_LINE_TO));
self.vertices.push((self.x2 - bw, self.y1 + bw, PATH_CMD_LINE_TO));
}
fn calc_text(&mut self) {
self.vertices.clear();
let dy = self.text_height * 2.0;
for (i, item) in self.items.iter().enumerate() {
let mut txt = GsvText::new();
txt.start_point(self.xs1 + dy * 1.5, self.ys1 + dy * (i as f64 + 1.0) - dy / 2.0);
txt.size(self.text_height, self.text_width);
txt.text(item);
let mut stroke = ConvStroke::new(txt);
stroke.set_width(self.text_thickness);
stroke.set_line_join(LineJoin::Round);
stroke.set_line_cap(LineCap::Round);
stroke.rewind(0);
loop {
let (mut x, mut y) = (0.0, 0.0);
let cmd = stroke.vertex(&mut x, &mut y);
if is_stop(cmd) {
break;
}
self.vertices.push((x, y, cmd));
}
}
}
fn calc_inactive_circles(&mut self) {
self.vertices.clear();
let dy = self.text_height * 2.0;
let r = self.text_height / 1.5;
for i in 0..self.items.len() {
let cx = self.xs1 + dy / 1.3;
let cy = self.ys1 + dy * i as f64 + dy / 1.3;
let mut ell = Ellipse::new(cx, cy, r, r, 32, false);
let mut stroke = ConvStroke::new(&mut ell);
stroke.set_width(self.text_thickness);
stroke.rewind(0);
loop {
let (mut x, mut y) = (0.0, 0.0);
let cmd = stroke.vertex(&mut x, &mut y);
if is_stop(cmd) {
break;
}
self.vertices.push((x, y, cmd));
}
}
}
fn calc_active_circle(&mut self) {
self.vertices.clear();
if self.cur_item < 0 {
return;
}
let dy = self.text_height * 2.0;
let cx = self.xs1 + dy / 1.3;
let cy = self.ys1 + dy * self.cur_item as f64 + dy / 1.3;
let r = self.text_height / 2.0;
let mut ell = Ellipse::new(cx, cy, r, r, 32, false);
ell.rewind(0);
loop {
let (mut x, mut y) = (0.0, 0.0);
let cmd = ell.vertex(&mut x, &mut y);
if is_stop(cmd) {
break;
}
self.vertices.push((x, y, cmd));
}
}
}
impl Ctrl for RboxCtrl {
fn num_paths(&self) -> u32 {
5
}
fn color(&self, path_id: u32) -> Rgba8 {
self.colors[path_id.min(4) as usize]
}
}
impl VertexSource for RboxCtrl {
fn rewind(&mut self, path_id: u32) {
self.vertex_idx = 0;
self.dy = self.text_height * 2.0;
match path_id {
0 => self.calc_background(),
1 => self.calc_border(),
2 => self.calc_text(),
3 => self.calc_inactive_circles(),
4 => self.calc_active_circle(),
_ => {
self.vertices.clear();
}
}
}
fn vertex(&mut self, x: &mut f64, y: &mut f64) -> u32 {
if self.vertex_idx < self.vertices.len() {
let (vx, vy, cmd) = self.vertices[self.vertex_idx];
*x = vx;
*y = vy;
self.vertex_idx += 1;
cmd
} else {
PATH_CMD_STOP
}
}
}
pub struct SplineCtrl {
x1: f64,
y1: f64,
x2: f64,
y2: f64,
num_pnt: usize,
xp: [f64; 32],
yp: [f64; 32],
spline_values: [f64; 256],
spline_values8: [u8; 256],
border_width: f64,
border_extra: f64,
curve_width: f64,
point_size: f64,
xs1: f64,
ys1: f64,
xs2: f64,
ys2: f64,
active_pnt: i32,
colors: [Rgba8; 5],
vertices: Vec<(f64, f64, u32)>,
vertex_idx: usize,
}
impl SplineCtrl {
pub fn new(x1: f64, y1: f64, x2: f64, y2: f64, num_pnt: usize) -> Self {
let n = num_pnt.clamp(4, 32);
let mut s = Self {
x1,
y1,
x2,
y2,
num_pnt: n,
xp: [0.0; 32],
yp: [0.0; 32],
spline_values: [0.0; 256],
spline_values8: [0; 256],
border_width: 1.0,
border_extra: 0.0,
curve_width: 1.0,
point_size: 3.0,
xs1: 0.0,
ys1: 0.0,
xs2: 0.0,
ys2: 0.0,
active_pnt: -1,
colors: [
Rgba8::new(255, 255, 230, 255), Rgba8::new(0, 0, 0, 255), Rgba8::new(0, 0, 0, 255), Rgba8::new(0, 0, 0, 255), Rgba8::new(255, 0, 0, 255), ],
vertices: Vec::new(),
vertex_idx: 0,
};
for i in 0..s.num_pnt {
s.xp[i] = i as f64 / (s.num_pnt - 1) as f64;
s.yp[i] = 0.5;
}
s.calc_spline_box();
s.update_spline();
s
}
pub fn border_width(&mut self, t: f64, extra: f64) {
self.border_width = t;
self.border_extra = extra;
self.calc_spline_box();
}
pub fn curve_width(&mut self, t: f64) {
self.curve_width = t;
}
pub fn point_size(&mut self, s: f64) {
self.point_size = s;
}
pub fn active_point(&mut self, i: i32) {
self.active_pnt = i;
}
pub fn background_color(&mut self, c: Rgba8) {
self.colors[0] = c;
}
pub fn border_color(&mut self, c: Rgba8) {
self.colors[1] = c;
}
pub fn curve_color(&mut self, c: Rgba8) {
self.colors[2] = c;
}
pub fn inactive_pnt_color(&mut self, c: Rgba8) {
self.colors[3] = c;
}
pub fn active_pnt_color(&mut self, c: Rgba8) {
self.colors[4] = c;
}
fn calc_spline_box(&mut self) {
self.xs1 = self.x1 + self.border_width;
self.ys1 = self.y1 + self.border_width;
self.xs2 = self.x2 - self.border_width;
self.ys2 = self.y2 - self.border_width;
}
fn set_xp(&mut self, idx: usize, mut val: f64) {
val = val.clamp(0.0, 1.0);
if idx == 0 {
val = 0.0;
} else if idx == self.num_pnt - 1 {
val = 1.0;
} else {
if val < self.xp[idx - 1] + 0.001 {
val = self.xp[idx - 1] + 0.001;
}
if val > self.xp[idx + 1] - 0.001 {
val = self.xp[idx + 1] - 0.001;
}
}
self.xp[idx] = val;
}
fn set_yp(&mut self, idx: usize, val: f64) {
self.yp[idx] = val.clamp(0.0, 1.0);
}
pub fn point(&mut self, idx: usize, x: f64, y: f64) {
if idx < self.num_pnt {
self.set_xp(idx, x);
self.set_yp(idx, y);
}
}
pub fn value(&self, x: f64) -> f64 {
let mut s = Bspline::new();
s.init(&self.xp[..self.num_pnt], &self.yp[..self.num_pnt]);
s.get(x).clamp(0.0, 1.0)
}
pub fn value_at_point(&mut self, idx: usize, y: f64) {
if idx < self.num_pnt {
self.set_yp(idx, y);
}
}
pub fn x(&self, idx: usize) -> f64 {
self.xp[idx]
}
pub fn y(&self, idx: usize) -> f64 {
self.yp[idx]
}
pub fn update_spline(&mut self) {
let mut spline = Bspline::new();
spline.init(&self.xp[..self.num_pnt], &self.yp[..self.num_pnt]);
for i in 0..256 {
let v = spline.get(i as f64 / 255.0).clamp(0.0, 1.0);
self.spline_values[i] = v;
self.spline_values8[i] = (v * 255.0) as u8;
}
}
pub fn spline(&self) -> &[f64; 256] {
&self.spline_values
}
pub fn spline8(&self) -> &[u8; 256] {
&self.spline_values8
}
fn calc_xp(&self, idx: usize) -> f64 {
self.xs1 + (self.xs2 - self.xs1) * self.xp[idx]
}
fn calc_yp(&self, idx: usize) -> f64 {
self.ys1 + (self.ys2 - self.ys1) * self.yp[idx]
}
fn calc_background(&mut self) {
self.vertices.clear();
let be = self.border_extra;
self.vertices
.push((self.x1 - be, self.y1 - be, PATH_CMD_MOVE_TO));
self.vertices
.push((self.x2 + be, self.y1 - be, PATH_CMD_LINE_TO));
self.vertices
.push((self.x2 + be, self.y2 + be, PATH_CMD_LINE_TO));
self.vertices
.push((self.x1 - be, self.y2 + be, PATH_CMD_LINE_TO));
}
fn calc_border(&mut self) {
self.vertices.clear();
self.vertices.push((self.x1, self.y1, PATH_CMD_MOVE_TO));
self.vertices.push((self.x2, self.y1, PATH_CMD_LINE_TO));
self.vertices.push((self.x2, self.y2, PATH_CMD_LINE_TO));
self.vertices.push((self.x1, self.y2, PATH_CMD_LINE_TO));
self.vertices.push((
self.x1 + self.border_width,
self.y1 + self.border_width,
PATH_CMD_MOVE_TO,
));
self.vertices.push((
self.x1 + self.border_width,
self.y2 - self.border_width,
PATH_CMD_LINE_TO,
));
self.vertices.push((
self.x2 - self.border_width,
self.y2 - self.border_width,
PATH_CMD_LINE_TO,
));
self.vertices.push((
self.x2 - self.border_width,
self.y1 + self.border_width,
PATH_CMD_LINE_TO,
));
}
fn calc_curve(&mut self) {
self.vertices.clear();
let mut path = PathStorage::new();
path.move_to(self.xs1, self.ys1 + (self.ys2 - self.ys1) * self.spline_values[0]);
for i in 1..256 {
path.line_to(
self.xs1 + (self.xs2 - self.xs1) * i as f64 / 255.0,
self.ys1 + (self.ys2 - self.ys1) * self.spline_values[i],
);
}
let mut stroke = ConvStroke::new(&mut path);
stroke.set_width(self.curve_width);
stroke.rewind(0);
loop {
let (mut x, mut y) = (0.0, 0.0);
let cmd = stroke.vertex(&mut x, &mut y);
if is_stop(cmd) {
break;
}
self.vertices.push((x, y, cmd));
}
}
fn calc_inactive_points(&mut self) {
self.vertices.clear();
for i in 0..self.num_pnt {
if i as i32 == self.active_pnt {
continue;
}
let mut ell = Ellipse::new(
self.calc_xp(i),
self.calc_yp(i),
self.point_size,
self.point_size,
32,
false,
);
ell.rewind(0);
loop {
let (mut x, mut y) = (0.0, 0.0);
let cmd = ell.vertex(&mut x, &mut y);
if is_stop(cmd) {
break;
}
self.vertices.push((x, y, cmd));
}
}
}
fn calc_active_point(&mut self) {
self.vertices.clear();
if self.active_pnt < 0 || self.active_pnt as usize >= self.num_pnt {
return;
}
let i = self.active_pnt as usize;
let mut ell = Ellipse::new(
self.calc_xp(i),
self.calc_yp(i),
self.point_size,
self.point_size,
32,
false,
);
ell.rewind(0);
loop {
let (mut x, mut y) = (0.0, 0.0);
let cmd = ell.vertex(&mut x, &mut y);
if is_stop(cmd) {
break;
}
self.vertices.push((x, y, cmd));
}
}
}
impl Ctrl for SplineCtrl {
fn num_paths(&self) -> u32 {
5
}
fn color(&self, path_id: u32) -> Rgba8 {
self.colors[path_id.min(4) as usize]
}
}
impl VertexSource for SplineCtrl {
fn rewind(&mut self, path_id: u32) {
self.vertex_idx = 0;
match path_id {
0 => self.calc_background(),
1 => self.calc_border(),
2 => self.calc_curve(),
3 => self.calc_inactive_points(),
4 => self.calc_active_point(),
_ => self.vertices.clear(),
}
}
fn vertex(&mut self, x: &mut f64, y: &mut f64) -> u32 {
if self.vertex_idx < self.vertices.len() {
let (vx, vy, cmd) = self.vertices[self.vertex_idx];
*x = vx;
*y = vy;
self.vertex_idx += 1;
cmd
} else {
PATH_CMD_STOP
}
}
}
pub struct GammaCtrl {
x1: f64,
y1: f64,
x2: f64,
y2: f64,
gamma_spline: crate::gamma::GammaSpline,
border_width: f64,
border_extra: f64,
curve_width: f64,
grid_width: f64,
text_thickness: f64,
point_size: f64,
text_height: f64,
text_width: f64,
xc1: f64,
yc1: f64,
xc2: f64,
yc2: f64,
xs1: f64,
ys1: f64,
xs2: f64,
ys2: f64,
xt1: f64,
yt1: f64,
#[allow(dead_code)]
xt2: f64,
#[allow(dead_code)]
yt2: f64,
xp1: f64,
yp1: f64,
xp2: f64,
yp2: f64,
p1_active: bool,
mouse_point: u32,
pdx: f64,
pdy: f64,
colors: [Rgba8; 7],
vertices: Vec<(f64, f64, u32)>,
vertex_idx: usize,
}
impl GammaCtrl {
pub fn new(x1: f64, y1: f64, x2: f64, y2: f64) -> Self {
let text_height = 9.0;
let border_width = 2.0;
let yc2 = y2 - text_height * 2.0;
let mut gc = Self {
x1,
y1,
x2,
y2,
gamma_spline: crate::gamma::GammaSpline::new(),
border_width,
border_extra: 0.0,
curve_width: 2.0,
grid_width: 0.2,
text_thickness: 1.5,
point_size: 5.0,
text_height,
text_width: 0.0,
xc1: x1,
yc1: y1,
xc2: x2,
yc2,
xs1: 0.0,
ys1: 0.0,
xs2: 0.0,
ys2: 0.0,
xt1: x1,
yt1: yc2,
xt2: x2,
yt2: y2,
xp1: 0.0,
yp1: 0.0,
xp2: 0.0,
yp2: 0.0,
p1_active: true,
mouse_point: 0,
pdx: 0.0,
pdy: 0.0,
colors: [
Rgba8::new(255, 255, 230, 255), Rgba8::new(0, 0, 0, 255), Rgba8::new(0, 0, 0, 255), Rgba8::new(51, 51, 0, 255), Rgba8::new(0, 0, 0, 255), Rgba8::new(255, 0, 0, 255), Rgba8::new(0, 0, 0, 255), ],
vertices: Vec::new(),
vertex_idx: 0,
};
gc.calc_spline_box();
gc
}
fn calc_spline_box(&mut self) {
self.xs1 = self.xc1 + self.border_width;
self.ys1 = self.yc1 + self.border_width;
self.xs2 = self.xc2 - self.border_width;
self.ys2 = self.yc2 - self.border_width * 0.5;
}
fn calc_points(&mut self) {
let (kx1, ky1, kx2, ky2) = self.gamma_spline.get_values();
self.xp1 = self.xs1 + (self.xs2 - self.xs1) * kx1 * 0.25;
self.yp1 = self.ys1 + (self.ys2 - self.ys1) * ky1 * 0.25;
self.xp2 = self.xs2 - (self.xs2 - self.xs1) * kx2 * 0.25;
self.yp2 = self.ys2 - (self.ys2 - self.ys1) * ky2 * 0.25;
}
fn calc_values(&mut self) {
let kx1 = (self.xp1 - self.xs1) * 4.0 / (self.xs2 - self.xs1);
let ky1 = (self.yp1 - self.ys1) * 4.0 / (self.ys2 - self.ys1);
let kx2 = (self.xs2 - self.xp2) * 4.0 / (self.xs2 - self.xs1);
let ky2 = (self.ys2 - self.yp2) * 4.0 / (self.ys2 - self.ys1);
self.gamma_spline.set_values(kx1, ky1, kx2, ky2);
}
pub fn border_width(&mut self, t: f64, extra: f64) {
self.border_width = t;
self.border_extra = extra;
self.calc_spline_box();
}
pub fn curve_width(&mut self, t: f64) {
self.curve_width = t;
}
pub fn grid_width(&mut self, t: f64) {
self.grid_width = t;
}
pub fn text_thickness(&mut self, t: f64) {
self.text_thickness = t;
}
pub fn text_size(&mut self, h: f64, w: f64) {
self.text_width = w;
self.text_height = h;
self.yc2 = self.y2 - self.text_height * 2.0;
self.yt1 = self.y2 - self.text_height * 2.0;
self.calc_spline_box();
}
pub fn point_size(&mut self, s: f64) {
self.point_size = s;
}
pub fn set_values(&mut self, kx1: f64, ky1: f64, kx2: f64, ky2: f64) {
self.gamma_spline.set_values(kx1, ky1, kx2, ky2);
}
pub fn get_values(&self) -> (f64, f64, f64, f64) {
self.gamma_spline.get_values()
}
pub fn gamma(&self) -> &[u8; 256] {
self.gamma_spline.gamma()
}
pub fn y(&self, x: f64) -> f64 {
self.gamma_spline.y(x)
}
pub fn get_gamma_spline(&self) -> &crate::gamma::GammaSpline {
&self.gamma_spline
}
pub fn change_active_point(&mut self) {
self.p1_active = !self.p1_active;
}
pub fn background_color(&mut self, c: Rgba8) {
self.colors[0] = c;
}
pub fn border_color(&mut self, c: Rgba8) {
self.colors[1] = c;
}
pub fn curve_color(&mut self, c: Rgba8) {
self.colors[2] = c;
}
pub fn grid_color(&mut self, c: Rgba8) {
self.colors[3] = c;
}
pub fn inactive_pnt_color(&mut self, c: Rgba8) {
self.colors[4] = c;
}
pub fn active_pnt_color(&mut self, c: Rgba8) {
self.colors[5] = c;
}
pub fn text_color(&mut self, c: Rgba8) {
self.colors[6] = c;
}
pub fn in_rect(&self, x: f64, y: f64) -> bool {
x >= self.x1 && x <= self.x2 && y >= self.y1 && y <= self.y2
}
pub fn on_mouse_button_down(&mut self, x: f64, y: f64) -> bool {
self.calc_points();
let dist1 = ((x - self.xp1).powi(2) + (y - self.yp1).powi(2)).sqrt();
if dist1 <= self.point_size + 1.0 {
self.mouse_point = 1;
self.pdx = self.xp1 - x;
self.pdy = self.yp1 - y;
self.p1_active = true;
return true;
}
let dist2 = ((x - self.xp2).powi(2) + (y - self.yp2).powi(2)).sqrt();
if dist2 <= self.point_size + 1.0 {
self.mouse_point = 2;
self.pdx = self.xp2 - x;
self.pdy = self.yp2 - y;
self.p1_active = false;
return true;
}
false
}
pub fn on_mouse_button_up(&mut self, _x: f64, _y: f64) -> bool {
if self.mouse_point != 0 {
self.mouse_point = 0;
return true;
}
false
}
pub fn on_mouse_move(&mut self, x: f64, y: f64, button_flag: bool) -> bool {
if !button_flag {
return self.on_mouse_button_up(x, y);
}
if self.mouse_point == 1 {
self.xp1 = x + self.pdx;
self.yp1 = y + self.pdy;
self.calc_values();
return true;
}
if self.mouse_point == 2 {
self.xp2 = x + self.pdx;
self.yp2 = y + self.pdy;
self.calc_values();
return true;
}
false
}
pub fn on_arrow_keys(&mut self, left: bool, right: bool, down: bool, up: bool) -> bool {
let (mut kx1, mut ky1, mut kx2, mut ky2) = self.gamma_spline.get_values();
let mut ret = false;
if self.p1_active {
if left {
kx1 -= 0.005;
ret = true;
}
if right {
kx1 += 0.005;
ret = true;
}
if down {
ky1 -= 0.005;
ret = true;
}
if up {
ky1 += 0.005;
ret = true;
}
} else {
if left {
kx2 += 0.005;
ret = true;
}
if right {
kx2 -= 0.005;
ret = true;
}
if down {
ky2 += 0.005;
ret = true;
}
if up {
ky2 -= 0.005;
ret = true;
}
}
if ret {
self.gamma_spline.set_values(kx1, ky1, kx2, ky2);
}
ret
}
fn calc_background(&mut self) {
self.vertices.clear();
let be = self.border_extra;
self.vertices
.push((self.x1 - be, self.y1 - be, PATH_CMD_MOVE_TO));
self.vertices
.push((self.x2 + be, self.y1 - be, PATH_CMD_LINE_TO));
self.vertices
.push((self.x2 + be, self.y2 + be, PATH_CMD_LINE_TO));
self.vertices
.push((self.x1 - be, self.y2 + be, PATH_CMD_LINE_TO));
}
fn calc_border(&mut self) {
self.vertices.clear();
let bw = self.border_width;
self.vertices
.push((self.x1, self.y1, PATH_CMD_MOVE_TO));
self.vertices
.push((self.x2, self.y1, PATH_CMD_LINE_TO));
self.vertices
.push((self.x2, self.y2, PATH_CMD_LINE_TO));
self.vertices
.push((self.x1, self.y2, PATH_CMD_LINE_TO));
self.vertices
.push((self.x1 + bw, self.y1 + bw, PATH_CMD_MOVE_TO));
self.vertices
.push((self.x1 + bw, self.y2 - bw, PATH_CMD_LINE_TO));
self.vertices
.push((self.x2 - bw, self.y2 - bw, PATH_CMD_LINE_TO));
self.vertices
.push((self.x2 - bw, self.y1 + bw, PATH_CMD_LINE_TO));
self.vertices
.push((self.xc1 + bw, self.yc2 - bw * 0.5, PATH_CMD_MOVE_TO));
self.vertices
.push((self.xc2 - bw, self.yc2 - bw * 0.5, PATH_CMD_LINE_TO));
self.vertices
.push((self.xc2 - bw, self.yc2 + bw * 0.5, PATH_CMD_LINE_TO));
self.vertices
.push((self.xc1 + bw, self.yc2 + bw * 0.5, PATH_CMD_LINE_TO));
}
fn calc_curve(&mut self) {
self.vertices.clear();
self.gamma_spline
.set_box(self.xs1, self.ys1, self.xs2, self.ys2);
let mut spline_copy = crate::gamma::GammaSpline::new();
let (kx1, ky1, kx2, ky2) = self.gamma_spline.get_values();
spline_copy.set_values(kx1, ky1, kx2, ky2);
spline_copy.set_box(self.xs1, self.ys1, self.xs2, self.ys2);
let mut path_verts = Vec::new();
spline_copy.rewind(0);
loop {
let (mut x, mut y) = (0.0, 0.0);
let cmd = spline_copy.vertex(&mut x, &mut y);
if is_stop(cmd) {
break;
}
path_verts.push((x, y, cmd));
}
let mut path = crate::path_storage::PathStorage::new();
for (i, &(x, y, cmd)) in path_verts.iter().enumerate() {
if i == 0 {
path.move_to(x, y);
} else {
path.line_to(x, y);
}
let _ = cmd;
}
let mut stroke = ConvStroke::new(&mut path);
stroke.set_width(self.curve_width);
stroke.rewind(0);
loop {
let (mut x, mut y) = (0.0, 0.0);
let cmd = stroke.vertex(&mut x, &mut y);
if is_stop(cmd) {
break;
}
self.vertices.push((x, y, cmd));
}
}
fn calc_grid(&mut self) {
self.vertices.clear();
self.calc_points();
let gw = self.grid_width;
let (xs1, ys1, xs2, ys2) = (self.xs1, self.ys1, self.xs2, self.ys2);
let ymid = (ys1 + ys2) * 0.5;
let xmid = (xs1 + xs2) * 0.5;
self.vertices.push((xs1, ymid - gw * 0.5, PATH_CMD_MOVE_TO));
self.vertices.push((xs2, ymid - gw * 0.5, PATH_CMD_LINE_TO));
self.vertices.push((xs2, ymid + gw * 0.5, PATH_CMD_LINE_TO));
self.vertices.push((xs1, ymid + gw * 0.5, PATH_CMD_LINE_TO));
self.vertices.push((xmid - gw * 0.5, ys1, PATH_CMD_MOVE_TO));
self.vertices
.push((xmid - gw * 0.5, ys2, PATH_CMD_LINE_TO));
self.vertices
.push((xmid + gw * 0.5, ys2, PATH_CMD_LINE_TO));
self.vertices
.push((xmid + gw * 0.5, ys1, PATH_CMD_LINE_TO));
let (xp1, yp1) = (self.xp1, self.yp1);
self.vertices
.push((xs1, yp1 - gw * 0.5, PATH_CMD_MOVE_TO));
self.vertices
.push((xp1 - gw * 0.5, yp1 - gw * 0.5, PATH_CMD_LINE_TO));
self.vertices
.push((xp1 - gw * 0.5, ys1, PATH_CMD_LINE_TO));
self.vertices
.push((xp1 + gw * 0.5, ys1, PATH_CMD_LINE_TO));
self.vertices
.push((xp1 + gw * 0.5, yp1 + gw * 0.5, PATH_CMD_LINE_TO));
self.vertices
.push((xs1, yp1 + gw * 0.5, PATH_CMD_LINE_TO));
let (xp2, yp2) = (self.xp2, self.yp2);
self.vertices
.push((xs2, yp2 + gw * 0.5, PATH_CMD_MOVE_TO));
self.vertices
.push((xp2 + gw * 0.5, yp2 + gw * 0.5, PATH_CMD_LINE_TO));
self.vertices
.push((xp2 + gw * 0.5, ys2, PATH_CMD_LINE_TO));
self.vertices
.push((xp2 - gw * 0.5, ys2, PATH_CMD_LINE_TO));
self.vertices
.push((xp2 - gw * 0.5, yp2 - gw * 0.5, PATH_CMD_LINE_TO));
self.vertices
.push((xs2, yp2 - gw * 0.5, PATH_CMD_LINE_TO));
}
fn calc_inactive_point(&mut self) {
self.vertices.clear();
self.calc_points();
let (cx, cy) = if self.p1_active {
(self.xp2, self.yp2)
} else {
(self.xp1, self.yp1)
};
let mut ell = Ellipse::new(cx, cy, self.point_size, self.point_size, 32, false);
ell.rewind(0);
loop {
let (mut x, mut y) = (0.0, 0.0);
let cmd = ell.vertex(&mut x, &mut y);
if is_stop(cmd) {
break;
}
self.vertices.push((x, y, cmd));
}
}
fn calc_active_point(&mut self) {
self.vertices.clear();
self.calc_points();
let (cx, cy) = if self.p1_active {
(self.xp1, self.yp1)
} else {
(self.xp2, self.yp2)
};
let mut ell = Ellipse::new(cx, cy, self.point_size, self.point_size, 32, false);
ell.rewind(0);
loop {
let (mut x, mut y) = (0.0, 0.0);
let cmd = ell.vertex(&mut x, &mut y);
if is_stop(cmd) {
break;
}
self.vertices.push((x, y, cmd));
}
}
fn calc_text_display(&mut self) {
self.vertices.clear();
let (kx1, ky1, kx2, ky2) = self.gamma_spline.get_values();
let text = format!("{:.3} {:.3} {:.3} {:.3}", kx1, ky1, kx2, ky2);
let mut txt = GsvText::new();
txt.text(&text);
txt.size(self.text_height, self.text_width);
txt.start_point(
self.xt1 + self.border_width * 2.0,
(self.yt1 + self.y2) * 0.5 - self.text_height * 0.5,
);
let mut stroke = ConvStroke::new(txt);
stroke.set_width(self.text_thickness);
stroke.set_line_join(LineJoin::Round);
stroke.set_line_cap(LineCap::Round);
stroke.rewind(0);
loop {
let (mut x, mut y) = (0.0, 0.0);
let cmd = stroke.vertex(&mut x, &mut y);
if is_stop(cmd) {
break;
}
self.vertices.push((x, y, cmd));
}
}
}
impl Ctrl for GammaCtrl {
fn num_paths(&self) -> u32 {
7
}
fn color(&self, path_id: u32) -> Rgba8 {
self.colors[path_id.min(6) as usize]
}
}
impl VertexSource for GammaCtrl {
fn rewind(&mut self, path_id: u32) {
self.vertex_idx = 0;
match path_id {
0 => self.calc_background(),
1 => self.calc_border(),
2 => self.calc_curve(),
3 => self.calc_grid(),
4 => self.calc_inactive_point(),
5 => self.calc_active_point(),
6 => self.calc_text_display(),
_ => {
self.vertices.clear();
}
}
}
fn vertex(&mut self, x: &mut f64, y: &mut f64) -> u32 {
if self.vertex_idx < self.vertices.len() {
let (vx, vy, cmd) = self.vertices[self.vertex_idx];
*x = vx;
*y = vy;
self.vertex_idx += 1;
cmd
} else {
PATH_CMD_STOP
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_slider_ctrl_default_value() {
let s = SliderCtrl::new(5.0, 5.0, 300.0, 12.0);
assert!((s.value() - 0.5).abs() < 1e-10);
}
#[test]
fn test_slider_ctrl_set_value() {
let mut s = SliderCtrl::new(5.0, 5.0, 300.0, 12.0);
s.range(-180.0, 180.0);
s.set_value(90.0);
assert!((s.value() - 90.0).abs() < 1e-6);
}
#[test]
fn test_slider_ctrl_vertex_source() {
let mut s = SliderCtrl::new(5.0, 5.0, 300.0, 12.0);
s.rewind(0);
let mut count = 0;
loop {
let (mut x, mut y) = (0.0, 0.0);
let cmd = s.vertex(&mut x, &mut y);
if is_stop(cmd) {
break;
}
count += 1;
}
assert_eq!(count, 4);
}
#[test]
fn test_slider_ctrl_pointer_circle() {
let mut s = SliderCtrl::new(5.0, 5.0, 300.0, 12.0);
s.rewind(4);
let mut count = 0;
loop {
let (mut x, mut y) = (0.0, 0.0);
let cmd = s.vertex(&mut x, &mut y);
if is_stop(cmd) {
break;
}
count += 1;
}
assert!(count >= 32);
}
#[test]
fn test_slider_ctrl_text() {
let mut s = SliderCtrl::new(5.0, 5.0, 300.0, 12.0);
s.label("Angle=%3.2f");
s.set_value(45.0);
s.rewind(2);
let mut count = 0;
loop {
let (mut x, mut y) = (0.0, 0.0);
let cmd = s.vertex(&mut x, &mut y);
if is_stop(cmd) {
break;
}
count += 1;
}
assert!(count > 0);
}
#[test]
fn test_slider_ctrl_num_paths() {
let s = SliderCtrl::new(5.0, 5.0, 300.0, 12.0);
assert_eq!(s.num_paths(), 6);
}
#[test]
fn test_scale_ctrl_defaults() {
let s = ScaleCtrl::new(5.0, 5.0, 395.0, 12.0);
assert!((s.value1() - 0.3).abs() < 1e-10);
assert!((s.value2() - 0.7).abs() < 1e-10);
assert_eq!(s.num_paths(), 5);
}
#[test]
fn test_scale_ctrl_setters_clamp_and_gap() {
let mut s = ScaleCtrl::new(5.0, 5.0, 395.0, 12.0);
s.set_min_delta(0.1);
s.set_value1(0.95);
assert!(s.value1() <= s.value2() - 0.1 + 1e-10);
s.set_value2(-1.0);
assert!(s.value2() >= s.value1() + 0.1 - 1e-10);
}
#[test]
fn test_scale_ctrl_paths_emit_vertices() {
let mut s = ScaleCtrl::new(5.0, 5.0, 395.0, 12.0);
for path_id in 0..5 {
s.rewind(path_id);
let mut seen = 0;
loop {
let (mut x, mut y) = (0.0, 0.0);
let cmd = s.vertex(&mut x, &mut y);
if is_stop(cmd) {
break;
}
seen += 1;
}
assert!(seen > 0, "expected vertices for path {}", path_id);
}
}
#[test]
fn test_sprintf_format_various() {
assert_eq!(sprintf_format("Some Value=%1.0f", 40.0), "Some Value=40");
assert_eq!(sprintf_format("Some Value=%1.0f", 32.0), "Some Value=32");
assert_eq!(sprintf_format("Spiral=%.3f", 0.0), "Spiral=0.000");
assert_eq!(sprintf_format("Spiral=%.3f", 0.05), "Spiral=0.050");
assert_eq!(sprintf_format("N=%.2f", 4.0), "N=4.00");
assert_eq!(sprintf_format("Angle=%3.2f", 45.0), "Angle=45.00");
assert_eq!(sprintf_format("Num Points=%.0f", 200.0), "Num Points=200");
assert_eq!(sprintf_format("Count=%d", 42.7), "Count=42");
assert_eq!(sprintf_format("radius=%4.3f", 1.5), "radius=1.500");
assert_eq!(sprintf_format("Hello", 42.0), "Hello");
assert_eq!(sprintf_format("Val=%f", 3.14), "Val=3.140000");
}
#[test]
fn test_cbox_ctrl_basic() {
let mut c = CboxCtrl::new(10.0, 10.0, "Outline");
assert!(!c.status());
c.set_status(true);
assert!(c.status());
assert_eq!(c.num_paths(), 3);
}
#[test]
fn test_cbox_ctrl_checkmark_only_when_active() {
let mut c = CboxCtrl::new(10.0, 10.0, "Test");
c.set_status(false);
c.rewind(2);
let (mut x, mut y) = (0.0, 0.0);
let cmd = c.vertex(&mut x, &mut y);
assert_eq!(cmd, PATH_CMD_STOP);
c.set_status(true);
c.rewind(2);
let cmd = c.vertex(&mut x, &mut y);
assert_eq!(cmd, PATH_CMD_MOVE_TO);
}
#[test]
fn test_rbox_ctrl_basic() {
let mut r = RboxCtrl::new(10.0, 10.0, 150.0, 100.0);
r.add_item("Option A");
r.add_item("Option B");
r.add_item("Option C");
r.set_cur_item(1);
assert_eq!(r.cur_item(), 1);
assert_eq!(r.num_paths(), 5);
}
#[test]
fn test_rbox_ctrl_inactive_circles() {
let mut r = RboxCtrl::new(10.0, 10.0, 150.0, 100.0);
r.add_item("A");
r.add_item("B");
r.rewind(3);
let mut count = 0;
loop {
let (mut x, mut y) = (0.0, 0.0);
let cmd = r.vertex(&mut x, &mut y);
if is_stop(cmd) {
break;
}
count += 1;
}
assert!(count > 32); }
#[test]
fn test_rbox_ctrl_no_active_when_negative() {
let mut r = RboxCtrl::new(10.0, 10.0, 150.0, 100.0);
r.add_item("X");
r.rewind(4);
let (mut x, mut y) = (0.0, 0.0);
let cmd = r.vertex(&mut x, &mut y);
assert_eq!(cmd, PATH_CMD_STOP);
}
#[test]
fn test_gamma_ctrl_basic() {
let gc = GammaCtrl::new(10.0, 10.0, 200.0, 200.0);
assert_eq!(gc.num_paths(), 7);
}
#[test]
fn test_gamma_ctrl_default_values() {
let gc = GammaCtrl::new(10.0, 10.0, 200.0, 200.0);
let (kx1, ky1, kx2, ky2) = gc.get_values();
assert!((kx1 - 1.0).abs() < 0.01);
assert!((ky1 - 1.0).abs() < 0.01);
assert!((kx2 - 1.0).abs() < 0.01);
assert!((ky2 - 1.0).abs() < 0.01);
}
#[test]
fn test_gamma_ctrl_set_values() {
let mut gc = GammaCtrl::new(10.0, 10.0, 200.0, 200.0);
gc.set_values(0.5, 1.5, 0.8, 1.2);
let (kx1, ky1, kx2, ky2) = gc.get_values();
assert!((kx1 - 0.5).abs() < 0.001);
assert!((ky1 - 1.5).abs() < 0.001);
assert!((kx2 - 0.8).abs() < 0.001);
assert!((ky2 - 1.2).abs() < 0.001);
}
#[test]
fn test_gamma_ctrl_gamma_table() {
let gc = GammaCtrl::new(10.0, 10.0, 200.0, 200.0);
let gamma = gc.gamma();
assert_eq!(gamma[0], 0);
assert_eq!(gamma[255], 255);
}
#[test]
fn test_gamma_ctrl_background_path() {
let mut gc = GammaCtrl::new(10.0, 10.0, 200.0, 200.0);
gc.rewind(0);
let mut count = 0;
loop {
let (mut x, mut y) = (0.0, 0.0);
let cmd = gc.vertex(&mut x, &mut y);
if is_stop(cmd) {
break;
}
count += 1;
}
assert_eq!(count, 4); }
#[test]
fn test_gamma_ctrl_border_path() {
let mut gc = GammaCtrl::new(10.0, 10.0, 200.0, 200.0);
gc.rewind(1);
let mut count = 0;
loop {
let (mut x, mut y) = (0.0, 0.0);
let cmd = gc.vertex(&mut x, &mut y);
if is_stop(cmd) {
break;
}
count += 1;
}
assert_eq!(count, 12); }
#[test]
fn test_gamma_ctrl_curve_path() {
let mut gc = GammaCtrl::new(10.0, 10.0, 200.0, 200.0);
gc.rewind(2);
let mut count = 0;
loop {
let (mut x, mut y) = (0.0, 0.0);
let cmd = gc.vertex(&mut x, &mut y);
if is_stop(cmd) {
break;
}
count += 1;
}
assert!(count > 10, "Curve path should have many vertices, got {count}");
}
#[test]
fn test_gamma_ctrl_grid_path() {
let mut gc = GammaCtrl::new(10.0, 10.0, 200.0, 200.0);
gc.rewind(3);
let mut count = 0;
loop {
let (mut x, mut y) = (0.0, 0.0);
let cmd = gc.vertex(&mut x, &mut y);
if is_stop(cmd) {
break;
}
count += 1;
}
assert_eq!(count, 20); }
#[test]
fn test_gamma_ctrl_points_path() {
let mut gc = GammaCtrl::new(10.0, 10.0, 200.0, 200.0);
gc.rewind(4);
let mut count = 0;
loop {
let (mut x, mut y) = (0.0, 0.0);
let cmd = gc.vertex(&mut x, &mut y);
if is_stop(cmd) {
break;
}
count += 1;
}
assert!(count >= 32);
gc.rewind(5);
let mut count = 0;
loop {
let (mut x, mut y) = (0.0, 0.0);
let cmd = gc.vertex(&mut x, &mut y);
if is_stop(cmd) {
break;
}
count += 1;
}
assert!(count >= 32);
}
#[test]
fn test_gamma_ctrl_text_path() {
let mut gc = GammaCtrl::new(10.0, 10.0, 200.0, 200.0);
gc.rewind(6);
let mut count = 0;
loop {
let (mut x, mut y) = (0.0, 0.0);
let cmd = gc.vertex(&mut x, &mut y);
if is_stop(cmd) {
break;
}
count += 1;
}
assert!(count > 0, "Text path should have vertices");
}
#[test]
fn test_gamma_ctrl_mouse_interaction() {
let mut gc = GammaCtrl::new(0.0, 0.0, 200.0, 200.0);
assert!(gc.in_rect(100.0, 100.0));
assert!(!gc.in_rect(300.0, 300.0));
let changed = gc.on_arrow_keys(false, true, false, false);
assert!(changed);
let (kx1, _, _, _) = gc.get_values();
assert!((kx1 - 1.005).abs() < 0.001);
}
}