301 lines
7.7 KiB
Rust
301 lines
7.7 KiB
Rust
use egui::Widget;
|
|
use hcie_engine_api::Engine;
|
|
use hcie_engine_api::{Tool, ZOOM_MAX, ZOOM_MIN};
|
|
use hcie_gui_egui::app::{AppDocument, SelectionTransform, ToolState, TransformHandle};
|
|
use hcie_gui_egui::canvas::CanvasWidget;
|
|
use hcie_gui_egui::event_bus::EventBus;
|
|
|
|
fn build_canvas() -> (
|
|
AppDocument,
|
|
ToolState,
|
|
EventBus,
|
|
Option<egui::TextureHandle>,
|
|
) {
|
|
(
|
|
AppDocument::new("Canvas", 100, 100),
|
|
ToolState::default(),
|
|
EventBus::new(),
|
|
None,
|
|
)
|
|
}
|
|
|
|
#[test]
|
|
fn canvas_widget_creation() {
|
|
egui::__run_test_ui(|ui| {
|
|
let (mut doc, mut state, mut bus, mut tex) = build_canvas();
|
|
let canvas = CanvasWidget::new(&mut doc, &mut state, &mut bus, &mut tex, 0, 0);
|
|
canvas.ui(ui);
|
|
});
|
|
}
|
|
|
|
fn test_zoom(zoom: f32) {
|
|
egui::__run_test_ui(|ui| {
|
|
let (mut doc, mut state, mut bus, mut tex) = build_canvas();
|
|
doc.zoom = zoom;
|
|
let canvas = CanvasWidget::new(&mut doc, &mut state, &mut bus, &mut tex, 0, 0);
|
|
canvas.ui(ui);
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn canvas_widget_zoom_0_1() {
|
|
test_zoom(0.1);
|
|
}
|
|
#[test]
|
|
fn canvas_widget_zoom_0_5() {
|
|
test_zoom(0.5);
|
|
}
|
|
#[test]
|
|
fn canvas_widget_zoom_1_0() {
|
|
test_zoom(1.0);
|
|
}
|
|
#[test]
|
|
fn canvas_widget_zoom_2_0() {
|
|
test_zoom(2.0);
|
|
}
|
|
#[test]
|
|
fn canvas_widget_zoom_4_0() {
|
|
test_zoom(4.0);
|
|
}
|
|
#[test]
|
|
fn canvas_widget_zoom_8_0() {
|
|
test_zoom(8.0);
|
|
}
|
|
#[test]
|
|
fn canvas_widget_zoom_min() {
|
|
test_zoom(ZOOM_MIN);
|
|
}
|
|
#[test]
|
|
fn canvas_widget_zoom_max() {
|
|
test_zoom(ZOOM_MAX);
|
|
}
|
|
|
|
fn test_tool(tool: Tool) {
|
|
egui::__run_test_ui(|ui| {
|
|
let (mut doc, mut state, mut bus, mut tex) = build_canvas();
|
|
state.active_tool = tool;
|
|
let canvas = CanvasWidget::new(&mut doc, &mut state, &mut bus, &mut tex, 0, 0);
|
|
canvas.ui(ui);
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn canvas_widget_pen() {
|
|
test_tool(Tool::Pen);
|
|
}
|
|
#[test]
|
|
fn canvas_widget_brush() {
|
|
test_tool(Tool::Brush);
|
|
}
|
|
#[test]
|
|
fn canvas_widget_eraser() {
|
|
test_tool(Tool::Eraser);
|
|
}
|
|
#[test]
|
|
fn canvas_widget_rect() {
|
|
test_tool(Tool::VectorRect);
|
|
}
|
|
#[test]
|
|
fn canvas_widget_line() {
|
|
test_tool(Tool::VectorLine);
|
|
}
|
|
#[test]
|
|
fn canvas_widget_select() {
|
|
test_tool(Tool::Select);
|
|
}
|
|
#[test]
|
|
fn canvas_widget_lasso() {
|
|
test_tool(Tool::Lasso);
|
|
}
|
|
#[test]
|
|
fn canvas_widget_magic_wand() {
|
|
test_tool(Tool::MagicWand);
|
|
}
|
|
#[test]
|
|
fn canvas_widget_eyedropper() {
|
|
test_tool(Tool::Eyedropper);
|
|
}
|
|
#[test]
|
|
fn canvas_widget_flood_fill() {
|
|
test_tool(Tool::FloodFill);
|
|
}
|
|
#[test]
|
|
fn canvas_widget_gradient() {
|
|
test_tool(Tool::Gradient);
|
|
}
|
|
#[test]
|
|
fn canvas_widget_move() {
|
|
test_tool(Tool::Move);
|
|
}
|
|
#[test]
|
|
fn canvas_widget_crop() {
|
|
test_tool(Tool::Crop);
|
|
}
|
|
|
|
#[test]
|
|
fn canvas_widget_with_pan() {
|
|
egui::__run_test_ui(|ui| {
|
|
let (mut doc, mut state, mut bus, mut tex) = build_canvas();
|
|
state.pan_offset = egui::Vec2::new(50.0, -30.0);
|
|
let canvas = CanvasWidget::new(&mut doc, &mut state, &mut bus, &mut tex, 0, 0);
|
|
canvas.ui(ui);
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn canvas_widget_with_selection() {
|
|
egui::__run_test_ui(|ui| {
|
|
let (mut doc, mut state, mut bus, mut tex) = build_canvas();
|
|
state.selection_rect = Some([10, 10, 50, 50]);
|
|
let canvas = CanvasWidget::new(&mut doc, &mut state, &mut bus, &mut tex, 0, 0);
|
|
canvas.ui(ui);
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn canvas_widget_lasso_with_points() {
|
|
egui::__run_test_ui(|ui| {
|
|
let (mut doc, mut state, mut bus, mut tex) = build_canvas();
|
|
state.active_tool = Tool::Lasso;
|
|
state.lasso_points = vec![(0, 0), (10, 0), (10, 10), (0, 10)];
|
|
let canvas = CanvasWidget::new(&mut doc, &mut state, &mut bus, &mut tex, 0, 0);
|
|
canvas.ui(ui);
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn canvas_widget_text_active() {
|
|
egui::__run_test_ui(|ui| {
|
|
let (mut doc, mut state, mut bus, mut tex) = build_canvas();
|
|
state.active_tool = Tool::Text;
|
|
state.text_edit.active = true;
|
|
state.text_edit.canvas_x = 50;
|
|
state.text_edit.canvas_y = 50;
|
|
state.text_edit.draft_text = "Test".into();
|
|
let canvas = CanvasWidget::new(&mut doc, &mut state, &mut bus, &mut tex, 0, 0);
|
|
canvas.ui(ui);
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn app_document_zoom_roundtrip() {
|
|
let mut doc = AppDocument::new("ZoomTest", 200, 200);
|
|
let zooms = [
|
|
0.1, 0.25, 0.5, 0.75, 1.0, 1.5, 2.0, 4.0, 8.0, 16.0, 32.0, 64.0,
|
|
];
|
|
for &z in &zooms {
|
|
doc.zoom = z;
|
|
assert!((doc.zoom - z).abs() < 0.001);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn engine_composite_after_draw() {
|
|
let mut doc = AppDocument::new("EngineDraw", 20, 20);
|
|
doc.engine
|
|
.draw_filled_rect_rgba(5, 5, 10, 10, [0, 255, 0, 255]);
|
|
let pixels = doc.engine.get_composite_pixels();
|
|
assert_eq!(pixels.len(), 20 * 20 * 4);
|
|
let idx = (5 * 20 + 5) * 4;
|
|
assert_eq!(pixels[idx], 0);
|
|
assert_eq!(pixels[idx + 1], 255);
|
|
assert_eq!(pixels[idx + 2], 0);
|
|
assert_eq!(pixels[idx + 3], 255);
|
|
}
|
|
|
|
#[test]
|
|
fn engine_multi_layer_composite() {
|
|
let mut doc = AppDocument::new("MultiLayer", 10, 10);
|
|
let id = doc.engine.add_layer("top");
|
|
doc.engine.set_active_layer(id);
|
|
doc.engine
|
|
.draw_filled_rect_rgba(0, 0, 10, 10, [255, 0, 0, 255]);
|
|
let pixels = doc.engine.get_composite_pixels();
|
|
assert_eq!(pixels[0], 255);
|
|
}
|
|
|
|
#[test]
|
|
fn transform_handle_variants() {
|
|
assert!(matches!(TransformHandle::None, _));
|
|
assert!(matches!(TransformHandle::TopLeft, _));
|
|
assert!(matches!(TransformHandle::TopRight, _));
|
|
assert!(matches!(TransformHandle::BottomLeft, _));
|
|
assert!(matches!(TransformHandle::BottomRight, _));
|
|
assert!(matches!(TransformHandle::Top, _));
|
|
assert!(matches!(TransformHandle::Bottom, _));
|
|
assert!(matches!(TransformHandle::Left, _));
|
|
assert!(matches!(TransformHandle::Right, _));
|
|
assert!(matches!(TransformHandle::Move, _));
|
|
}
|
|
|
|
#[test]
|
|
fn selection_transform_from_engine_empty_mask() {
|
|
let engine = Engine::new(10, 10);
|
|
let st = SelectionTransform::from_engine(&engine, &[], 10, 10);
|
|
assert!(st.pixels.is_empty());
|
|
assert_eq!(st.width, 0);
|
|
assert_eq!(st.height, 0);
|
|
}
|
|
|
|
#[test]
|
|
fn zoom_clamp_values() {
|
|
let values = [-1.0, 0.0, 0.005, ZOOM_MIN, 0.5, 1.0, 2.0, ZOOM_MAX, 100.0];
|
|
for &v in &values {
|
|
let clamped = v.clamp(ZOOM_MIN, ZOOM_MAX);
|
|
assert!(
|
|
clamped >= ZOOM_MIN && clamped <= ZOOM_MAX,
|
|
"{} clamped to {} should be in [{}, {}]",
|
|
v,
|
|
clamped,
|
|
ZOOM_MIN,
|
|
ZOOM_MAX
|
|
);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn event_bus_events_dispatched_to_engine() {
|
|
let mut doc = AppDocument::new("EventEngine", 10, 10);
|
|
doc.engine.draw_filled_rect_rgba(0, 0, 10, 10, [100; 4]);
|
|
let before = doc.engine.get_composite_pixels()[0];
|
|
doc.engine.undo();
|
|
let after = doc.engine.get_composite_pixels()[0];
|
|
assert_ne!(before, after);
|
|
}
|
|
|
|
#[test]
|
|
fn test_text_layer_rotation() {
|
|
let mut doc = AppDocument::new("TextRotate", 200, 200);
|
|
let font_path = "../../assets/fonts/Inter.ttf";
|
|
if let Ok(bytes) = std::fs::read(font_path) {
|
|
let name = "Inter.ttf";
|
|
doc.engine.load_font(name, &bytes).unwrap();
|
|
let id = doc.engine.add_text(
|
|
"Rotated",
|
|
name,
|
|
24.0,
|
|
100.0,
|
|
100.0,
|
|
[255, 255, 255, 255],
|
|
45.0,
|
|
hcie_engine_api::TextAlignment::Left,
|
|
hcie_engine_api::TextOrientation::Horizontal,
|
|
&[],
|
|
);
|
|
assert_ne!(id, 0);
|
|
let pixels = doc.engine.get_composite_pixels();
|
|
let mut count = 0;
|
|
for i in 0..pixels.len() / 4 {
|
|
if pixels[i * 4 + 3] > 0 {
|
|
count += 1;
|
|
}
|
|
}
|
|
assert!(
|
|
count > 0,
|
|
"No rotated text pixels were rasterized onto the canvas!"
|
|
);
|
|
} else {
|
|
panic!("Failed to read Inter.ttf from {}", font_path);
|
|
}
|
|
}
|