Implement stable serialization and restoration for dock layouts, including auto-hidden and floating panels
- Add `persistence.rs` for stable serialization of dock layouts without runtime identifiers. - Introduce `preview.rs` for geometry handling of dock drop previews. - Create `sizing.rs` to manage content-aware sizing policies and constraint solving for dock panels. - Implement `welcome.rs` to provide a welcome surface when no documents are open, featuring New and Open actions.
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
use eframe::egui;
|
||||
use eframe::egui::Color32;
|
||||
use egui::ColorImage;
|
||||
use hcie_brush_engine::{BrushStyle, presets::BrushPreset};
|
||||
use hcie_brush_engine::{presets::BrushPreset, BrushStyle};
|
||||
|
||||
// ── tunables ─────────────────────────────────────────────────────────────────
|
||||
const TILE_SIZE: u32 = 512;
|
||||
@@ -23,38 +23,55 @@ struct SliderCaps {
|
||||
fn slider_caps(style: BrushStyle) -> SliderCaps {
|
||||
match style {
|
||||
BrushStyle::Calligraphy => SliderCaps {
|
||||
angle: true, roundness: true, jitter: false, scatter: false,
|
||||
spray_particle_size: false, spray_density: false,
|
||||
angle: true,
|
||||
roundness: true,
|
||||
jitter: false,
|
||||
scatter: false,
|
||||
spray_particle_size: false,
|
||||
spray_density: false,
|
||||
..SliderCaps::common()
|
||||
},
|
||||
BrushStyle::Pencil | BrushStyle::InkPen => SliderCaps {
|
||||
jitter: true, ..SliderCaps::common()
|
||||
jitter: true,
|
||||
..SliderCaps::common()
|
||||
},
|
||||
BrushStyle::Spray | BrushStyle::Airbrush => SliderCaps {
|
||||
jitter: true, scatter: true,
|
||||
spray_particle_size: true, spray_density: true,
|
||||
jitter: true,
|
||||
scatter: true,
|
||||
spray_particle_size: true,
|
||||
spray_density: true,
|
||||
..SliderCaps::common()
|
||||
},
|
||||
BrushStyle::Charcoal => SliderCaps {
|
||||
jitter: true, scatter: true, ..SliderCaps::common()
|
||||
jitter: true,
|
||||
scatter: true,
|
||||
..SliderCaps::common()
|
||||
},
|
||||
BrushStyle::Crayon => SliderCaps {
|
||||
jitter: true, scatter: true, ..SliderCaps::common()
|
||||
jitter: true,
|
||||
scatter: true,
|
||||
..SliderCaps::common()
|
||||
},
|
||||
BrushStyle::Tree => SliderCaps {
|
||||
jitter: true, angle: true, ..SliderCaps::common()
|
||||
jitter: true,
|
||||
angle: true,
|
||||
..SliderCaps::common()
|
||||
},
|
||||
BrushStyle::Meadow | BrushStyle::Rock | BrushStyle::Dirt => SliderCaps {
|
||||
jitter: true, ..SliderCaps::common()
|
||||
jitter: true,
|
||||
..SliderCaps::common()
|
||||
},
|
||||
BrushStyle::Bristle => SliderCaps {
|
||||
roundness: true, ..SliderCaps::common()
|
||||
roundness: true,
|
||||
..SliderCaps::common()
|
||||
},
|
||||
BrushStyle::HardRound | BrushStyle::SoftRound => SliderCaps {
|
||||
hardness: false, ..SliderCaps::common()
|
||||
hardness: false,
|
||||
..SliderCaps::common()
|
||||
},
|
||||
BrushStyle::Mixer | BrushStyle::Blender => SliderCaps {
|
||||
flow: false, ..SliderCaps::common()
|
||||
flow: false,
|
||||
..SliderCaps::common()
|
||||
},
|
||||
_ => SliderCaps::common(),
|
||||
}
|
||||
@@ -63,20 +80,32 @@ fn slider_caps(style: BrushStyle) -> SliderCaps {
|
||||
impl SliderCaps {
|
||||
fn common() -> Self {
|
||||
Self {
|
||||
hardness: true, opacity: true, spacing: true, flow: true,
|
||||
jitter: false, scatter: false, angle: false, roundness: false,
|
||||
spray_particle_size: false, spray_density: false,
|
||||
hardness: true,
|
||||
opacity: true,
|
||||
spacing: true,
|
||||
flow: true,
|
||||
jitter: false,
|
||||
scatter: false,
|
||||
angle: false,
|
||||
roundness: false,
|
||||
spray_particle_size: false,
|
||||
spray_density: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
enum CanvasPreset { S512, S1024, S2048, S4096 }
|
||||
enum CanvasPreset {
|
||||
S512,
|
||||
S1024,
|
||||
S2048,
|
||||
S4096,
|
||||
}
|
||||
|
||||
impl CanvasPreset {
|
||||
fn size(self) -> (u32, u32) {
|
||||
match self {
|
||||
Self::S512 => (512, 512),
|
||||
Self::S512 => (512, 512),
|
||||
Self::S1024 => (1024, 1024),
|
||||
Self::S2048 => (2048, 2048),
|
||||
Self::S4096 => (4096, 4096),
|
||||
@@ -84,7 +113,7 @@ impl CanvasPreset {
|
||||
}
|
||||
fn name(self) -> &'static str {
|
||||
match self {
|
||||
Self::S512 => "512",
|
||||
Self::S512 => "512",
|
||||
Self::S1024 => "1024",
|
||||
Self::S2048 => "2048",
|
||||
Self::S4096 => "4096",
|
||||
@@ -93,21 +122,26 @@ impl CanvasPreset {
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
enum FpsCap { Unlimited, Fps30, Fps60, Fps165 }
|
||||
enum FpsCap {
|
||||
Unlimited,
|
||||
Fps30,
|
||||
Fps60,
|
||||
Fps165,
|
||||
}
|
||||
|
||||
impl FpsCap {
|
||||
fn value(self) -> Option<f32> {
|
||||
match self {
|
||||
Self::Fps30 => Some(30.0),
|
||||
Self::Fps60 => Some(60.0),
|
||||
Self::Fps30 => Some(30.0),
|
||||
Self::Fps60 => Some(60.0),
|
||||
Self::Fps165 => Some(165.0),
|
||||
Self::Unlimited => None,
|
||||
}
|
||||
}
|
||||
fn name(self) -> &'static str {
|
||||
match self {
|
||||
Self::Fps30 => "30",
|
||||
Self::Fps60 => "60",
|
||||
Self::Fps30 => "30",
|
||||
Self::Fps60 => "60",
|
||||
Self::Fps165 => "165",
|
||||
Self::Unlimited => "∞",
|
||||
}
|
||||
@@ -137,7 +171,12 @@ impl TiledCanvas {
|
||||
let total = (cols * rows) as usize;
|
||||
Self {
|
||||
pixels: vec![255; (w * h * 4) as usize],
|
||||
tiles: (0..total).map(|_| Tile { texture: None, dirty: true }).collect(),
|
||||
tiles: (0..total)
|
||||
.map(|_| Tile {
|
||||
texture: None,
|
||||
dirty: true,
|
||||
})
|
||||
.collect(),
|
||||
w,
|
||||
h,
|
||||
cols,
|
||||
@@ -181,18 +220,28 @@ impl TiledCanvas {
|
||||
break;
|
||||
}
|
||||
let e2 = 2.0 * err;
|
||||
if e2 > -dy { err -= dy; x0 += sx; }
|
||||
if e2 < dx { err += dx; y0 += sy; }
|
||||
if e2 > -dy {
|
||||
err -= dy;
|
||||
x0 += sx;
|
||||
}
|
||||
if e2 < dx {
|
||||
err += dx;
|
||||
y0 += sy;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn mark_all_dirty(&mut self) {
|
||||
for t in &mut self.tiles { t.dirty = true; }
|
||||
for t in &mut self.tiles {
|
||||
t.dirty = true;
|
||||
}
|
||||
self.dirty_count = self.tiles.len();
|
||||
}
|
||||
|
||||
fn upload_dirty(&mut self, ctx: &egui::Context) {
|
||||
if self.dirty_count == 0 { return; }
|
||||
if self.dirty_count == 0 {
|
||||
return;
|
||||
}
|
||||
let rows = self.h.div_ceil(TILE_SIZE);
|
||||
for ty in 0..rows {
|
||||
let tile_h = if ty == rows - 1 {
|
||||
@@ -202,7 +251,9 @@ impl TiledCanvas {
|
||||
} as usize;
|
||||
for tx in 0..self.cols {
|
||||
let idx = (ty * self.cols + tx) as usize;
|
||||
if !self.tiles[idx].dirty { continue; }
|
||||
if !self.tiles[idx].dirty {
|
||||
continue;
|
||||
}
|
||||
self.tiles[idx].dirty = false;
|
||||
self.dirty_count = self.dirty_count.saturating_sub(1);
|
||||
|
||||
@@ -223,10 +274,7 @@ impl TiledCanvas {
|
||||
.copy_from_slice(&self.pixels[src..src + tile_w * 4]);
|
||||
}
|
||||
|
||||
let img = ColorImage::from_rgba_unmultiplied(
|
||||
[tile_w, tile_h],
|
||||
&data,
|
||||
);
|
||||
let img = ColorImage::from_rgba_unmultiplied([tile_w, tile_h], &data);
|
||||
let key = format!("t{}_{}", tx, ty);
|
||||
if let Some(tex) = &mut self.tiles[idx].texture {
|
||||
tex.set(img, egui::TextureOptions::NEAREST);
|
||||
@@ -274,7 +322,9 @@ impl App {
|
||||
}
|
||||
|
||||
fn set_canvas_size(&mut self, p: CanvasPreset) {
|
||||
if self.canvas_preset == p { return; }
|
||||
if self.canvas_preset == p {
|
||||
return;
|
||||
}
|
||||
self.canvas_preset = p;
|
||||
let (w, h) = p.size();
|
||||
self.canvas = TiledCanvas::new(w, h);
|
||||
@@ -325,8 +375,16 @@ impl eframe::App for App {
|
||||
egui::ComboBox::from_id_salt("canvas_preset")
|
||||
.selected_text(self.canvas_preset.name())
|
||||
.show_ui(ui, |ui| {
|
||||
for p in &[CanvasPreset::S512, CanvasPreset::S1024, CanvasPreset::S2048, CanvasPreset::S4096] {
|
||||
if ui.selectable_label(self.canvas_preset == *p, p.name()).clicked() {
|
||||
for p in &[
|
||||
CanvasPreset::S512,
|
||||
CanvasPreset::S1024,
|
||||
CanvasPreset::S2048,
|
||||
CanvasPreset::S4096,
|
||||
] {
|
||||
if ui
|
||||
.selectable_label(self.canvas_preset == *p, p.name())
|
||||
.clicked()
|
||||
{
|
||||
self.set_canvas_size(*p);
|
||||
}
|
||||
}
|
||||
@@ -336,7 +394,12 @@ impl eframe::App for App {
|
||||
egui::ComboBox::from_id_salt("fps_cap")
|
||||
.selected_text(self.fps_cap.name())
|
||||
.show_ui(ui, |ui| {
|
||||
for c in &[FpsCap::Fps30, FpsCap::Fps60, FpsCap::Fps165, FpsCap::Unlimited] {
|
||||
for c in &[
|
||||
FpsCap::Fps30,
|
||||
FpsCap::Fps60,
|
||||
FpsCap::Fps165,
|
||||
FpsCap::Unlimited,
|
||||
] {
|
||||
if ui.selectable_label(self.fps_cap == *c, c.name()).clicked() {
|
||||
self.fps_cap = *c;
|
||||
}
|
||||
@@ -355,16 +418,48 @@ impl eframe::App for App {
|
||||
|
||||
ui.add(egui::Slider::new(&mut self.preset.tip.size, 1.0..=500.0).text("Size"));
|
||||
let caps = slider_caps(self.preset.style);
|
||||
ui.add_enabled(caps.hardness, egui::Slider::new(&mut self.preset.tip.hardness, 0.0..=1.0).text("Hardness"));
|
||||
ui.add_enabled(caps.opacity, egui::Slider::new(&mut self.preset.tip.opacity, 0.0..=1.0).text("Opacity"));
|
||||
ui.add_enabled(caps.spacing, egui::Slider::new(&mut self.preset.tip.spacing, 0.01..=1.0).text("Spacing"));
|
||||
ui.add_enabled(caps.flow, egui::Slider::new(&mut self.preset.tip.flow, 0.01..=1.0).text("Flow"));
|
||||
ui.add_enabled(caps.jitter, egui::Slider::new(&mut self.preset.tip.jitter_amount, 0.0..=5.0).text("Jitter"));
|
||||
ui.add_enabled(caps.scatter, egui::Slider::new(&mut self.preset.tip.scatter_amount, 0.0..=1.0).text("Scatter"));
|
||||
ui.add_enabled(caps.angle, egui::Slider::new(&mut self.preset.tip.angle, 0.0..=360.0).text("Angle"));
|
||||
ui.add_enabled(caps.roundness, egui::Slider::new(&mut self.preset.tip.roundness, 0.0..=1.0).text("Roundness"));
|
||||
ui.add_enabled(caps.spray_particle_size, egui::Slider::new(&mut self.preset.tip.spray_particle_size, 0.5..=20.0).text("Spray Size"));
|
||||
ui.add_enabled(caps.spray_density, egui::Slider::new(&mut self.preset.tip.spray_density, 1..=500).text("Spray Density"));
|
||||
ui.add_enabled(
|
||||
caps.hardness,
|
||||
egui::Slider::new(&mut self.preset.tip.hardness, 0.0..=1.0).text("Hardness"),
|
||||
);
|
||||
ui.add_enabled(
|
||||
caps.opacity,
|
||||
egui::Slider::new(&mut self.preset.tip.opacity, 0.0..=1.0).text("Opacity"),
|
||||
);
|
||||
ui.add_enabled(
|
||||
caps.spacing,
|
||||
egui::Slider::new(&mut self.preset.tip.spacing, 0.01..=1.0).text("Spacing"),
|
||||
);
|
||||
ui.add_enabled(
|
||||
caps.flow,
|
||||
egui::Slider::new(&mut self.preset.tip.flow, 0.01..=1.0).text("Flow"),
|
||||
);
|
||||
ui.add_enabled(
|
||||
caps.jitter,
|
||||
egui::Slider::new(&mut self.preset.tip.jitter_amount, 0.0..=5.0).text("Jitter"),
|
||||
);
|
||||
ui.add_enabled(
|
||||
caps.scatter,
|
||||
egui::Slider::new(&mut self.preset.tip.scatter_amount, 0.0..=1.0).text("Scatter"),
|
||||
);
|
||||
ui.add_enabled(
|
||||
caps.angle,
|
||||
egui::Slider::new(&mut self.preset.tip.angle, 0.0..=360.0).text("Angle"),
|
||||
);
|
||||
ui.add_enabled(
|
||||
caps.roundness,
|
||||
egui::Slider::new(&mut self.preset.tip.roundness, 0.0..=1.0).text("Roundness"),
|
||||
);
|
||||
ui.add_enabled(
|
||||
caps.spray_particle_size,
|
||||
egui::Slider::new(&mut self.preset.tip.spray_particle_size, 0.5..=20.0)
|
||||
.text("Spray Size"),
|
||||
);
|
||||
ui.add_enabled(
|
||||
caps.spray_density,
|
||||
egui::Slider::new(&mut self.preset.tip.spray_density, 1..=500)
|
||||
.text("Spray Density"),
|
||||
);
|
||||
|
||||
let mut rgba = [
|
||||
self.color[0] as f32 / 255.,
|
||||
@@ -374,8 +469,10 @@ impl eframe::App for App {
|
||||
];
|
||||
if ui.color_edit_button_rgba_unmultiplied(&mut rgba).changed() {
|
||||
self.color = [
|
||||
(rgba[0] * 255.) as u8, (rgba[1] * 255.) as u8,
|
||||
(rgba[2] * 255.) as u8, (rgba[3] * 255.) as u8,
|
||||
(rgba[0] * 255.) as u8,
|
||||
(rgba[1] * 255.) as u8,
|
||||
(rgba[2] * 255.) as u8,
|
||||
(rgba[3] * 255.) as u8,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -399,7 +496,13 @@ impl eframe::App for App {
|
||||
ui.allocate_exact_size(egui::vec2(disp_w, disp_h), egui::Sense::drag());
|
||||
|
||||
// paint tiles
|
||||
if self.canvas.tiles.first().and_then(|t| t.texture.as_ref()).is_some() {
|
||||
if self
|
||||
.canvas
|
||||
.tiles
|
||||
.first()
|
||||
.and_then(|t| t.texture.as_ref())
|
||||
.is_some()
|
||||
{
|
||||
let rows = self.canvas.h.div_ceil(TILE_SIZE);
|
||||
for ty in 0..rows {
|
||||
for tx in 0..self.canvas.cols {
|
||||
@@ -438,7 +541,8 @@ impl eframe::App for App {
|
||||
if let Some(pos) = resp.interact_pointer_pos() {
|
||||
let cx = (pos.x - rect.min.x) / scale;
|
||||
let cy = (pos.y - rect.min.y) / scale;
|
||||
if cx >= 0.0 && cy >= 0.0
|
||||
if cx >= 0.0
|
||||
&& cy >= 0.0
|
||||
&& cx < self.canvas.w as f32
|
||||
&& cy < self.canvas.h as f32
|
||||
{
|
||||
@@ -450,8 +554,10 @@ impl eframe::App for App {
|
||||
};
|
||||
self.last_pt = Some(pt);
|
||||
self.canvas.mark_line_dirty(
|
||||
seg[0].0, seg[0].1,
|
||||
seg[seg.len() - 1].0, seg[seg.len() - 1].1,
|
||||
seg[0].0,
|
||||
seg[0].1,
|
||||
seg[seg.len() - 1].0,
|
||||
seg[seg.len() - 1].1,
|
||||
self.preset.tip.size,
|
||||
);
|
||||
hcie_brush_engine::draw_specialized_stroke(
|
||||
|
||||
@@ -5,9 +5,9 @@ use hcie_protocol::BrushTip;
|
||||
/// Per-stroke dynamic parameters computed from input state.
|
||||
#[derive(Debug, Clone, Copy, Default)]
|
||||
pub struct StrokeDynamics {
|
||||
pub pressure: f32, // 0.0–1.0
|
||||
pub velocity: f32, // Pixels per frame
|
||||
pub tilt: (f32, f32), // (x_tilt, y_tilt) in degrees
|
||||
pub pressure: f32, // 0.0–1.0
|
||||
pub velocity: f32, // Pixels per frame
|
||||
pub tilt: (f32, f32), // (x_tilt, y_tilt) in degrees
|
||||
}
|
||||
|
||||
/// Compute effective brush size from base size, dynamics, and pressure.
|
||||
@@ -22,7 +22,11 @@ pub fn effective_size(base_size: f32, dynamics: &StrokeDynamics, pressure_sensit
|
||||
}
|
||||
|
||||
/// Compute effective opacity from base opacity, dynamics, and pressure.
|
||||
pub fn effective_opacity(base_opacity: f32, dynamics: &StrokeDynamics, pressure_sensitive: bool) -> f32 {
|
||||
pub fn effective_opacity(
|
||||
base_opacity: f32,
|
||||
dynamics: &StrokeDynamics,
|
||||
pressure_sensitive: bool,
|
||||
) -> f32 {
|
||||
if !pressure_sensitive {
|
||||
return base_opacity;
|
||||
}
|
||||
@@ -47,7 +51,12 @@ pub fn velocity_size_modifier(velocity: f32) -> f32 {
|
||||
}
|
||||
|
||||
/// Build a `BrushTip` with dynamics applied.
|
||||
pub fn build_dynamic_tip(base: &BrushTip, dynamics: &StrokeDynamics, pressure_size: bool, pressure_opacity: bool) -> BrushTip {
|
||||
pub fn build_dynamic_tip(
|
||||
base: &BrushTip,
|
||||
dynamics: &StrokeDynamics,
|
||||
pressure_size: bool,
|
||||
pressure_opacity: bool,
|
||||
) -> BrushTip {
|
||||
let mut tip = base.clone();
|
||||
tip.size = effective_size(base.size, dynamics, pressure_size);
|
||||
tip.opacity = effective_opacity(base.opacity, dynamics, pressure_opacity);
|
||||
@@ -70,7 +79,9 @@ pub struct VelocityTracker {
|
||||
}
|
||||
|
||||
impl VelocityTracker {
|
||||
pub fn new() -> Self { Self::default() }
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
pub fn update(&mut self, x: f32, y: f32, time: f32) -> f32 {
|
||||
let velocity = match (self.last_pos, self.last_time) {
|
||||
@@ -96,8 +107,11 @@ impl VelocityTracker {
|
||||
pub fn jitter_color(color: [u8; 4], variation: u8, rng: &mut rand::rngs::ThreadRng) -> [u8; 4] {
|
||||
use rand::Rng;
|
||||
let jitter = variation as f32 / 255.0;
|
||||
let r = (color[0] as f32 * (1.0 - jitter) + rng.gen::<f32>() * jitter * 255.0).clamp(0.0, 255.0) as u8;
|
||||
let g = (color[1] as f32 * (1.0 - jitter) + rng.gen::<f32>() * jitter * 255.0).clamp(0.0, 255.0) as u8;
|
||||
let b = (color[2] as f32 * (1.0 - jitter) + rng.gen::<f32>() * jitter * 255.0).clamp(0.0, 255.0) as u8;
|
||||
let r = (color[0] as f32 * (1.0 - jitter) + rng.gen::<f32>() * jitter * 255.0).clamp(0.0, 255.0)
|
||||
as u8;
|
||||
let g = (color[1] as f32 * (1.0 - jitter) + rng.gen::<f32>() * jitter * 255.0).clamp(0.0, 255.0)
|
||||
as u8;
|
||||
let b = (color[2] as f32 * (1.0 - jitter) + rng.gen::<f32>() * jitter * 255.0).clamp(0.0, 255.0)
|
||||
as u8;
|
||||
[r, g, b, color[3]]
|
||||
}
|
||||
|
||||
+1169
-202
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
//! Brush presets factory — ported from V2 `hcie-core/src/brush.rs`.
|
||||
|
||||
use hcie_protocol::{BrushTip, BrushStyle};
|
||||
use hcie_protocol::{BrushStyle, BrushTip};
|
||||
|
||||
fn round_soft(size: f32) -> BrushTip {
|
||||
BrushTip {
|
||||
@@ -59,7 +59,8 @@ fn pencil(size: f32) -> BrushTip {
|
||||
opacity: 1.0,
|
||||
hardness: 1.0,
|
||||
spacing: 0.05,
|
||||
flow: 1.0, jitter_amount: 0.15,
|
||||
flow: 1.0,
|
||||
jitter_amount: 0.15,
|
||||
scatter_amount: 0.0,
|
||||
angle: 0.0,
|
||||
roundness: 1.0,
|
||||
@@ -83,7 +84,8 @@ fn spray(size: f32) -> BrushTip {
|
||||
opacity: 0.6,
|
||||
hardness: 0.3,
|
||||
spacing: 0.35,
|
||||
flow: 1.0, jitter_amount: 0.4,
|
||||
flow: 1.0,
|
||||
jitter_amount: 0.4,
|
||||
scatter_amount: 0.6,
|
||||
angle: 0.0,
|
||||
roundness: 1.0,
|
||||
@@ -107,7 +109,8 @@ fn calligraphy(size: f32) -> BrushTip {
|
||||
opacity: 0.9,
|
||||
hardness: 0.7,
|
||||
spacing: 0.08,
|
||||
flow: 1.0, jitter_amount: 0.0,
|
||||
flow: 1.0,
|
||||
jitter_amount: 0.0,
|
||||
scatter_amount: 0.0,
|
||||
angle: 45.0_f32.to_radians(),
|
||||
roundness: 0.15,
|
||||
@@ -196,8 +199,8 @@ impl BrushPreset {
|
||||
opacity: 0.4,
|
||||
hardness: 0.0,
|
||||
spacing: 0.03,
|
||||
flow: 1.0,
|
||||
jitter_amount: 0.0,
|
||||
flow: 1.0,
|
||||
jitter_amount: 0.0,
|
||||
scatter_amount: 0.0,
|
||||
angle: 0.0,
|
||||
roundness: 1.0,
|
||||
@@ -247,8 +250,8 @@ impl BrushPreset {
|
||||
opacity: 1.0,
|
||||
hardness: 0.95,
|
||||
spacing: 0.03,
|
||||
flow: 1.0,
|
||||
jitter_amount: 0.0,
|
||||
flow: 1.0,
|
||||
jitter_amount: 0.0,
|
||||
scatter_amount: 0.0,
|
||||
angle: 0.0,
|
||||
roundness: 1.0,
|
||||
@@ -283,8 +286,8 @@ impl BrushPreset {
|
||||
opacity: 0.7,
|
||||
hardness: 0.5,
|
||||
spacing: 0.15,
|
||||
flow: 1.0,
|
||||
jitter_amount: 2.0,
|
||||
flow: 1.0,
|
||||
jitter_amount: 2.0,
|
||||
scatter_amount: 0.5,
|
||||
angle: 0.0,
|
||||
roundness: 0.8,
|
||||
@@ -319,8 +322,8 @@ impl BrushPreset {
|
||||
opacity: 0.85,
|
||||
hardness: 0.9,
|
||||
spacing: 0.08,
|
||||
flow: 1.0,
|
||||
jitter_amount: 0.0,
|
||||
flow: 1.0,
|
||||
jitter_amount: 0.0,
|
||||
scatter_amount: 0.0,
|
||||
angle: 0.0,
|
||||
roundness: 1.0,
|
||||
@@ -355,8 +358,8 @@ impl BrushPreset {
|
||||
opacity: 0.25,
|
||||
hardness: 0.0,
|
||||
spacing: 0.08,
|
||||
flow: 1.0,
|
||||
jitter_amount: 0.0,
|
||||
flow: 1.0,
|
||||
jitter_amount: 0.0,
|
||||
scatter_amount: 0.0,
|
||||
angle: 0.0,
|
||||
roundness: 1.0,
|
||||
@@ -391,8 +394,8 @@ impl BrushPreset {
|
||||
opacity: 0.35,
|
||||
hardness: 0.0,
|
||||
spacing: 0.06,
|
||||
flow: 1.0,
|
||||
jitter_amount: 0.0,
|
||||
flow: 1.0,
|
||||
jitter_amount: 0.0,
|
||||
scatter_amount: 0.0,
|
||||
angle: 0.0,
|
||||
roundness: 1.0,
|
||||
@@ -427,8 +430,8 @@ impl BrushPreset {
|
||||
opacity: 0.95,
|
||||
hardness: 0.85,
|
||||
spacing: 0.04,
|
||||
flow: 1.0,
|
||||
jitter_amount: 0.0,
|
||||
flow: 1.0,
|
||||
jitter_amount: 0.0,
|
||||
scatter_amount: 0.0,
|
||||
angle: 0.0,
|
||||
roundness: 1.0,
|
||||
@@ -463,8 +466,8 @@ impl BrushPreset {
|
||||
opacity: 0.95,
|
||||
hardness: 0.7,
|
||||
spacing: 0.04,
|
||||
flow: 1.0,
|
||||
jitter_amount: 0.0,
|
||||
flow: 1.0,
|
||||
jitter_amount: 0.0,
|
||||
scatter_amount: 0.0,
|
||||
angle: 0.0,
|
||||
roundness: 1.0,
|
||||
@@ -499,8 +502,8 @@ impl BrushPreset {
|
||||
opacity: 0.85,
|
||||
hardness: 0.5,
|
||||
spacing: 0.12,
|
||||
flow: 1.0,
|
||||
jitter_amount: 1.0,
|
||||
flow: 1.0,
|
||||
jitter_amount: 1.0,
|
||||
scatter_amount: 0.2,
|
||||
angle: 0.0,
|
||||
roundness: 1.0,
|
||||
@@ -612,8 +615,8 @@ impl BrushPreset {
|
||||
opacity: 0.9,
|
||||
hardness: 0.8,
|
||||
spacing: 0.40,
|
||||
flow: 1.0,
|
||||
jitter_amount: 0.8,
|
||||
flow: 1.0,
|
||||
jitter_amount: 0.8,
|
||||
scatter_amount: 0.0,
|
||||
angle: 0.0,
|
||||
roundness: 1.0,
|
||||
@@ -648,8 +651,8 @@ impl BrushPreset {
|
||||
opacity: 0.75,
|
||||
hardness: 0.6,
|
||||
spacing: 0.35,
|
||||
flow: 1.0,
|
||||
jitter_amount: 0.3,
|
||||
flow: 1.0,
|
||||
jitter_amount: 0.3,
|
||||
scatter_amount: 0.0,
|
||||
angle: 0.0,
|
||||
roundness: 1.0,
|
||||
@@ -699,8 +702,8 @@ impl BrushPreset {
|
||||
opacity: 0.85,
|
||||
hardness: 0.7,
|
||||
spacing: 0.18,
|
||||
flow: 1.0,
|
||||
jitter_amount: 0.5,
|
||||
flow: 1.0,
|
||||
jitter_amount: 0.5,
|
||||
scatter_amount: 0.0,
|
||||
angle: 0.0,
|
||||
roundness: 1.0,
|
||||
@@ -735,8 +738,8 @@ impl BrushPreset {
|
||||
opacity: 0.9,
|
||||
hardness: 0.6,
|
||||
spacing: 0.30,
|
||||
flow: 1.0,
|
||||
jitter_amount: 0.3,
|
||||
flow: 1.0,
|
||||
jitter_amount: 0.3,
|
||||
scatter_amount: 0.0,
|
||||
angle: 90.0,
|
||||
roundness: 1.0,
|
||||
@@ -773,8 +776,8 @@ impl BrushPreset {
|
||||
opacity: 0.8,
|
||||
hardness: 1.0,
|
||||
spacing: 0.40,
|
||||
flow: 1.0,
|
||||
jitter_amount: 4.0,
|
||||
flow: 1.0,
|
||||
jitter_amount: 4.0,
|
||||
scatter_amount: 0.6,
|
||||
angle: 0.0,
|
||||
roundness: 1.0,
|
||||
@@ -839,8 +842,8 @@ impl BrushPreset {
|
||||
opacity: 0.9,
|
||||
hardness: 0.8,
|
||||
spacing: 0.03,
|
||||
flow: 1.0,
|
||||
jitter_amount: 0.0,
|
||||
flow: 1.0,
|
||||
jitter_amount: 0.0,
|
||||
scatter_amount: 0.0,
|
||||
angle: 0.0,
|
||||
roundness: 0.8,
|
||||
@@ -943,4 +946,4 @@ impl BrushPreset {
|
||||
Self::sketch(),
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use hcie_brush_engine::{BrushTip, BrushStyle};
|
||||
use hcie_brush_engine::{BrushStyle, BrushTip};
|
||||
|
||||
fn make_round_tip(size: f32, hardness: f32) -> BrushTip {
|
||||
BrushTip {
|
||||
@@ -14,7 +14,11 @@ fn test_generate_brush_stamp_size() {
|
||||
let tip = make_round_tip(10.0, 0.5);
|
||||
let stamp = hcie_brush_engine::generate_brush_stamp(&tip);
|
||||
let expected_d = 20usize;
|
||||
assert_eq!(stamp.len(), expected_d * expected_d, "stamp should be square");
|
||||
assert_eq!(
|
||||
stamp.len(),
|
||||
expected_d * expected_d,
|
||||
"stamp should be square"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -30,7 +34,11 @@ fn test_generate_brush_stamp_center_nonzero() {
|
||||
fn test_generate_brush_stamp_hardness_gradient() {
|
||||
let soft = hcie_brush_engine::generate_brush_stamp(&make_round_tip(10.0, 0.0));
|
||||
let hard = hcie_brush_engine::generate_brush_stamp(&make_round_tip(10.0, 1.0));
|
||||
assert_eq!(soft.len(), hard.len(), "same size stamp should have same length");
|
||||
assert_eq!(
|
||||
soft.len(),
|
||||
hard.len(),
|
||||
"same size stamp should have same length"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -38,7 +46,11 @@ fn test_brush_spacing_pixels() {
|
||||
let spacing = hcie_brush_engine::brush_spacing_pixels(20.0, 0.5);
|
||||
assert_eq!(spacing, 10.0, "spacing should be size * ratio");
|
||||
let min_spacing = hcie_brush_engine::brush_spacing_pixels(20.0, 0.0);
|
||||
assert!((min_spacing - 0.2).abs() < 0.001, "min spacing should be ~0.2, got {}", min_spacing);
|
||||
assert!(
|
||||
(min_spacing - 0.2).abs() < 0.001,
|
||||
"min spacing should be ~0.2, got {}",
|
||||
min_spacing
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user