Add transparent bg and make raster and vector tools use same colors
This commit is contained in:
parent
1c3f794958
commit
6162adfa9f
|
|
@ -438,10 +438,38 @@ pub fn render_document_with_transform(
|
|||
/// Draw the document background
|
||||
fn render_background(document: &Document, scene: &mut Scene, base_transform: Affine) {
|
||||
let background_rect = Rect::new(0.0, 0.0, document.width, document.height);
|
||||
let bg = &document.background_color;
|
||||
|
||||
// Convert our ShapeColor to vello's peniko Color
|
||||
let background_color = document.background_color.to_peniko();
|
||||
// Draw checkerboard behind transparent backgrounds
|
||||
if bg.a < 255 {
|
||||
use vello::peniko::{Blob, Color, Extend, ImageAlphaType, ImageData, ImageQuality};
|
||||
// 2x2 pixel checkerboard pattern: light/dark alternating
|
||||
let light: [u8; 4] = [204, 204, 204, 255];
|
||||
let dark: [u8; 4] = [170, 170, 170, 255];
|
||||
let pixels: Vec<u8> = [light, dark, dark, light].concat();
|
||||
let image_data = ImageData {
|
||||
data: Blob::from(pixels),
|
||||
format: ImageFormat::Rgba8,
|
||||
width: 2,
|
||||
height: 2,
|
||||
alpha_type: ImageAlphaType::AlphaPremultiplied,
|
||||
};
|
||||
let brush = ImageBrush::new(image_data)
|
||||
.with_extend(Extend::Repeat)
|
||||
.with_quality(ImageQuality::Low);
|
||||
// Scale each pixel to 16x16 document units
|
||||
let brush_transform = Affine::scale(16.0);
|
||||
scene.fill(
|
||||
Fill::NonZero,
|
||||
base_transform,
|
||||
&brush,
|
||||
Some(brush_transform),
|
||||
&background_rect,
|
||||
);
|
||||
}
|
||||
|
||||
// Draw the background color on top (alpha-blended)
|
||||
let background_color = bg.to_peniko();
|
||||
scene.fill(
|
||||
Fill::NonZero,
|
||||
base_transform,
|
||||
|
|
|
|||
|
|
@ -761,6 +761,7 @@ struct EditorApp {
|
|||
brush_opacity: f32, // brush opacity 0.0–1.0
|
||||
brush_hardness: f32, // brush hardness 0.0–1.0
|
||||
brush_spacing: f32, // dabs_per_radius (fraction of radius per dab)
|
||||
brush_use_fg: bool, // true = paint with FG (stroke) color, false = BG (fill) color
|
||||
// Audio engine integration
|
||||
#[allow(dead_code)] // Must be kept alive to maintain audio output
|
||||
audio_stream: Option<cpal::Stream>,
|
||||
|
|
@ -1039,6 +1040,7 @@ impl EditorApp {
|
|||
brush_opacity: 1.0,
|
||||
brush_hardness: 0.5,
|
||||
brush_spacing: 0.1,
|
||||
brush_use_fg: true,
|
||||
audio_stream,
|
||||
audio_controller,
|
||||
audio_event_rx,
|
||||
|
|
@ -5158,6 +5160,7 @@ impl eframe::App for EditorApp {
|
|||
brush_opacity: &mut self.brush_opacity,
|
||||
brush_hardness: &mut self.brush_hardness,
|
||||
brush_spacing: &mut self.brush_spacing,
|
||||
brush_use_fg: &mut self.brush_use_fg,
|
||||
audio_controller: self.audio_controller.as_ref(),
|
||||
video_manager: &self.video_manager,
|
||||
playback_time: &mut self.playback_time,
|
||||
|
|
|
|||
|
|
@ -302,6 +302,14 @@ impl InfopanelPane {
|
|||
|
||||
// Raster paint tools
|
||||
Tool::Draw | Tool::Erase | Tool::Smudge if is_raster_paint_tool => {
|
||||
// Color source toggle (Draw tool only)
|
||||
if matches!(tool, Tool::Draw) {
|
||||
ui.horizontal(|ui| {
|
||||
ui.label("Color:");
|
||||
ui.selectable_value(shared.brush_use_fg, true, "FG");
|
||||
ui.selectable_value(shared.brush_use_fg, false, "BG");
|
||||
});
|
||||
}
|
||||
ui.horizontal(|ui| {
|
||||
ui.label("Size:");
|
||||
ui.add(
|
||||
|
|
@ -374,20 +382,9 @@ impl InfopanelPane {
|
|||
ui.label("Fill:");
|
||||
match info.fill_color {
|
||||
Some(Some(color)) => {
|
||||
let mut egui_color = egui::Color32::from_rgba_unmultiplied(
|
||||
color.r, color.g, color.b, color.a,
|
||||
);
|
||||
if egui::color_picker::color_edit_button_srgba(
|
||||
ui,
|
||||
&mut egui_color,
|
||||
egui::color_picker::Alpha::OnlyBlend,
|
||||
).changed() {
|
||||
let new_color = ShapeColor {
|
||||
r: egui_color.r(),
|
||||
g: egui_color.g(),
|
||||
b: egui_color.b(),
|
||||
a: egui_color.a(),
|
||||
};
|
||||
let mut rgba = [color.r, color.g, color.b, color.a];
|
||||
if ui.color_edit_button_srgba_unmultiplied(&mut rgba).changed() {
|
||||
let new_color = ShapeColor::rgba(rgba[0], rgba[1], rgba[2], rgba[3]);
|
||||
let action = SetShapePropertiesAction::set_fill_color(
|
||||
layer_id, time, face_ids.clone(), Some(new_color),
|
||||
);
|
||||
|
|
@ -408,20 +405,9 @@ impl InfopanelPane {
|
|||
ui.label("Stroke:");
|
||||
match info.stroke_color {
|
||||
Some(Some(color)) => {
|
||||
let mut egui_color = egui::Color32::from_rgba_unmultiplied(
|
||||
color.r, color.g, color.b, color.a,
|
||||
);
|
||||
if egui::color_picker::color_edit_button_srgba(
|
||||
ui,
|
||||
&mut egui_color,
|
||||
egui::color_picker::Alpha::OnlyBlend,
|
||||
).changed() {
|
||||
let new_color = ShapeColor {
|
||||
r: egui_color.r(),
|
||||
g: egui_color.g(),
|
||||
b: egui_color.b(),
|
||||
a: egui_color.a(),
|
||||
};
|
||||
let mut rgba = [color.r, color.g, color.b, color.a];
|
||||
if ui.color_edit_button_srgba_unmultiplied(&mut rgba).changed() {
|
||||
let new_color = ShapeColor::rgba(rgba[0], rgba[1], rgba[2], rgba[3]);
|
||||
let action = SetShapePropertiesAction::set_stroke_color(
|
||||
layer_id, time, edge_ids.clone(), Some(new_color),
|
||||
);
|
||||
|
|
@ -538,14 +524,14 @@ impl InfopanelPane {
|
|||
}
|
||||
});
|
||||
|
||||
// Background color
|
||||
// Background color (with alpha)
|
||||
ui.horizontal(|ui| {
|
||||
ui.label("Background:");
|
||||
let bg = document.background_color;
|
||||
let mut color = [bg.r, bg.g, bg.b];
|
||||
if ui.color_edit_button_srgb(&mut color).changed() {
|
||||
let mut color = [bg.r, bg.g, bg.b, bg.a];
|
||||
if ui.color_edit_button_srgba_unmultiplied(&mut color).changed() {
|
||||
let action = SetDocumentPropertiesAction::set_background_color(
|
||||
ShapeColor::rgb(color[0], color[1], color[2]),
|
||||
ShapeColor::rgba(color[0], color[1], color[2], color[3]),
|
||||
);
|
||||
shared.pending_actions.push(Box::new(action));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -192,6 +192,8 @@ pub struct SharedPaneState<'a> {
|
|||
pub brush_opacity: &'a mut f32,
|
||||
pub brush_hardness: &'a mut f32,
|
||||
pub brush_spacing: &'a mut f32,
|
||||
/// Whether the brush paints with the foreground (fill) color (true) or background (stroke) color (false)
|
||||
pub brush_use_fg: &'a mut bool,
|
||||
/// Audio engine controller for playback control (wrapped in Arc<Mutex<>> for thread safety)
|
||||
pub audio_controller: Option<&'a std::sync::Arc<std::sync::Mutex<daw_backend::EngineController>>>,
|
||||
/// Video manager for video decoding and frame caching
|
||||
|
|
|
|||
|
|
@ -4420,7 +4420,7 @@ impl StagePane {
|
|||
let color = if matches!(blend_mode, lightningbeam_core::raster_layer::RasterBlendMode::Erase) {
|
||||
[1.0f32, 1.0, 1.0, 1.0]
|
||||
} else {
|
||||
let c = *shared.stroke_color;
|
||||
let c = if *shared.brush_use_fg { *shared.stroke_color } else { *shared.fill_color };
|
||||
[c.r() as f32 / 255.0, c.g() as f32 / 255.0, c.b() as f32 / 255.0, c.a() as f32 / 255.0]
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -205,19 +205,49 @@ impl PaneRenderer for ToolbarPane {
|
|||
let color_row_width = fill_label_width + color_button_size + button_spacing;
|
||||
let color_x = rect.left() + (rect.width() - color_row_width) / 2.0;
|
||||
|
||||
// For raster layers show a single "Color" swatch (brush paint color = stroke_color).
|
||||
// For vector layers show Fill + Stroke.
|
||||
if !is_raster {
|
||||
// Fill color label
|
||||
// Two color swatches:
|
||||
// Stroke/FG always on top, Fill/BG always on bottom.
|
||||
// Raster layers label them "FG" / "BG"; vector layers label them "Stroke" / "Fill".
|
||||
{
|
||||
let stroke_label = if is_raster { "FG" } else { "Stroke" };
|
||||
ui.painter().text(
|
||||
egui::pos2(color_x + fill_label_width / 2.0, y + color_button_size / 2.0),
|
||||
egui::Align2::CENTER_CENTER,
|
||||
"Fill",
|
||||
stroke_label,
|
||||
egui::FontId::proportional(14.0),
|
||||
egui::Color32::from_gray(200),
|
||||
);
|
||||
|
||||
let stroke_button_rect = egui::Rect::from_min_size(
|
||||
egui::pos2(color_x + fill_label_width + button_spacing, y),
|
||||
egui::vec2(color_button_size, color_button_size),
|
||||
);
|
||||
let stroke_button_id = ui.id().with(("stroke_color_button", path));
|
||||
let stroke_response = ui.interact(stroke_button_rect, stroke_button_id, egui::Sense::click());
|
||||
draw_color_button(ui, stroke_button_rect, *shared.stroke_color);
|
||||
egui::containers::Popup::from_toggle_button_response(&stroke_response)
|
||||
.show(|ui| {
|
||||
ui.spacing_mut().slider_width = 275.0;
|
||||
let changed = egui::color_picker::color_picker_color32(ui, shared.stroke_color, egui::color_picker::Alpha::OnlyBlend);
|
||||
if changed {
|
||||
*shared.active_color_mode = super::ColorMode::Stroke;
|
||||
}
|
||||
});
|
||||
|
||||
y += color_button_size + button_spacing;
|
||||
}
|
||||
|
||||
// Fill/BG color swatch
|
||||
{
|
||||
let fill_label = if is_raster { "BG" } else { "Fill" };
|
||||
ui.painter().text(
|
||||
egui::pos2(color_x + fill_label_width / 2.0, y + color_button_size / 2.0),
|
||||
egui::Align2::CENTER_CENTER,
|
||||
fill_label,
|
||||
egui::FontId::proportional(14.0),
|
||||
egui::Color32::from_gray(200),
|
||||
);
|
||||
|
||||
// Fill color button
|
||||
let fill_button_rect = egui::Rect::from_min_size(
|
||||
egui::pos2(color_x + fill_label_width + button_spacing, y),
|
||||
egui::vec2(color_button_size, color_button_size),
|
||||
|
|
@ -227,40 +257,13 @@ impl PaneRenderer for ToolbarPane {
|
|||
draw_color_button(ui, fill_button_rect, *shared.fill_color);
|
||||
egui::containers::Popup::from_toggle_button_response(&fill_response)
|
||||
.show(|ui| {
|
||||
ui.spacing_mut().slider_width = 275.0;
|
||||
let changed = egui::color_picker::color_picker_color32(ui, shared.fill_color, egui::color_picker::Alpha::OnlyBlend);
|
||||
if changed {
|
||||
*shared.active_color_mode = super::ColorMode::Fill;
|
||||
}
|
||||
});
|
||||
|
||||
y += color_button_size + button_spacing;
|
||||
}
|
||||
|
||||
// Stroke/brush color label
|
||||
let stroke_label = if is_raster { "Color" } else { "Stroke" };
|
||||
ui.painter().text(
|
||||
egui::pos2(color_x + fill_label_width / 2.0, y + color_button_size / 2.0),
|
||||
egui::Align2::CENTER_CENTER,
|
||||
stroke_label,
|
||||
egui::FontId::proportional(14.0),
|
||||
egui::Color32::from_gray(200),
|
||||
);
|
||||
|
||||
// Stroke color button
|
||||
let stroke_button_rect = egui::Rect::from_min_size(
|
||||
egui::pos2(color_x + fill_label_width + button_spacing, y),
|
||||
egui::vec2(color_button_size, color_button_size),
|
||||
);
|
||||
let stroke_button_id = ui.id().with(("stroke_color_button", path));
|
||||
let stroke_response = ui.interact(stroke_button_rect, stroke_button_id, egui::Sense::click());
|
||||
draw_color_button(ui, stroke_button_rect, *shared.stroke_color);
|
||||
egui::containers::Popup::from_toggle_button_response(&stroke_response)
|
||||
.show(|ui| {
|
||||
let changed = egui::color_picker::color_picker_color32(ui, shared.stroke_color, egui::color_picker::Alpha::OnlyBlend);
|
||||
if changed {
|
||||
*shared.active_color_mode = super::ColorMode::Stroke;
|
||||
}
|
||||
});
|
||||
} // end color pickers
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue