Add a checkered background to transparent artboards and the infinite canvas (#4022)

* Add checkered transparency rendering to infinite canvas and artboards

* Enable artboard clipping by default

* Make new infinite canvas documents begin with a white background layer

* Remove the export dialog's transparency option now that it's redundant

* Make exporting transparent JPGs use white not black

* Code review
This commit is contained in:
Keavon Chambers
2026-04-10 03:21:21 -07:00
committed by GitHub
parent 661e8bc569
commit da45ab2f87
21 changed files with 245 additions and 100 deletions
@@ -6,7 +6,6 @@ use crate::messages::prelude::*;
pub enum ExportDialogMessage {
FileType { file_type: FileType },
ScaleFactor { factor: f64 },
TransparentBackground { transparent: bool },
ExportBounds { bounds: ExportBounds },
Submit,
@@ -14,7 +14,6 @@ pub struct ExportDialogMessageHandler {
pub file_type: FileType,
pub scale_factor: f64,
pub bounds: ExportBounds,
pub transparent_background: bool,
pub artboards: HashMap<LayerNodeIdentifier, String>,
pub has_selection: bool,
}
@@ -25,7 +24,6 @@ impl Default for ExportDialogMessageHandler {
file_type: Default::default(),
scale_factor: 1.,
bounds: Default::default(),
transparent_background: false,
artboards: Default::default(),
has_selection: false,
}
@@ -40,11 +38,17 @@ impl MessageHandler<ExportDialogMessage, ExportDialogMessageContext<'_>> for Exp
match message {
ExportDialogMessage::FileType { file_type } => self.file_type = file_type,
ExportDialogMessage::ScaleFactor { factor } => self.scale_factor = factor,
ExportDialogMessage::TransparentBackground { transparent } => self.transparent_background = transparent,
ExportDialogMessage::ExportBounds { bounds } => self.bounds = bounds,
ExportDialogMessage::Submit => {
let artboard_name = match self.bounds {
// Fall back to "All Artwork" if "Selection" was chosen but nothing is currently selected
let bounds = if !self.has_selection && self.bounds == ExportBounds::Selection {
ExportBounds::AllArtwork
} else {
self.bounds
};
let artboard_name = match bounds {
ExportBounds::Artboard(layer) => self.artboards.get(&layer).cloned(),
_ => None,
};
@@ -52,8 +56,7 @@ impl MessageHandler<ExportDialogMessage, ExportDialogMessageContext<'_>> for Exp
name: portfolio.active_document().map(|document| document.name.clone()).unwrap_or_default(),
file_type: self.file_type,
scale_factor: self.scale_factor,
bounds: self.bounds,
transparent_background: self.file_type != FileType::Jpg && self.transparent_background,
bounds,
artboard_name,
artboard_count: self.artboards.len(),
})
@@ -127,6 +130,7 @@ impl LayoutHolder for ExportDialogMessageHandler {
let artboards = self.artboards.iter().map(|(&layer, name)| (ExportBounds::Artboard(layer), name.to_string(), false)).collect();
let choices = [standard_bounds, artboards];
// Fall back to "All Artwork" if "Selection" was chosen but nothing is currently selected
let current_bounds = if !self.has_selection && self.bounds == ExportBounds::Selection {
ExportBounds::AllArtwork
} else {
@@ -159,22 +163,6 @@ impl LayoutHolder for ExportDialogMessageHandler {
DropdownInput::new(entries).selected_index(Some(index as u32)).widget_instance(),
];
let checkbox_id = CheckboxId::new();
let transparent_background = vec![
TextLabel::new("Transparency").table_align(true).min_width(100).for_checkbox(checkbox_id).widget_instance(),
Separator::new(SeparatorStyle::Unrelated).widget_instance(),
CheckboxInput::new(self.transparent_background)
.disabled(self.file_type == FileType::Jpg)
.on_update(move |value: &CheckboxInput| ExportDialogMessage::TransparentBackground { transparent: value.checked }.into())
.for_label(checkbox_id)
.widget_instance(),
];
Layout(vec![
LayoutGroup::row(export_type),
LayoutGroup::row(resolution),
LayoutGroup::row(export_area),
LayoutGroup::row(transparent_background),
])
Layout(vec![LayoutGroup::row(export_type), LayoutGroup::row(resolution), LayoutGroup::row(export_area)])
}
}
@@ -1,7 +1,9 @@
use crate::messages::layout::utility_types::widget_prelude::*;
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
use crate::messages::prelude::*;
use glam::{IVec2, UVec2};
use graph_craft::document::NodeId;
use graphene_std::Color;
/// A dialog to allow users to set some initial options about a new document.
#[derive(Debug, Clone, Default, ExtractField)]
@@ -22,26 +24,40 @@ impl MessageHandler<NewDocumentDialogMessage, ()> for NewDocumentDialogMessageHa
NewDocumentDialogMessage::Submit => {
responses.add(PortfolioMessage::NewDocumentWithName { name: self.name.clone() });
let create_artboard = !self.infinite && self.dimensions.x > 0 && self.dimensions.y > 0;
if create_artboard {
if self.infinite {
// Infinite canvas: add a locked white background layer
let node_id = NodeId::new();
responses.add(GraphOperationMessage::NewColorFillLayer {
node_id,
color: Color::WHITE,
parent: LayerNodeIdentifier::ROOT_PARENT,
insert_index: 0,
});
responses.add(NodeGraphMessage::SetDisplayNameImpl {
node_id,
alias: "Background".to_string(),
});
responses.add(NodeGraphMessage::SetLocked { node_id, locked: true });
} else if self.dimensions.x > 0 && self.dimensions.y > 0 {
// Finite canvas: create an artboard with the specified dimensions
responses.add(GraphOperationMessage::NewArtboard {
id: NodeId::new(),
artboard: graphene_std::Artboard::new(IVec2::ZERO, self.dimensions.as_ivec2()),
});
responses.add(NavigationMessage::CanvasPan { delta: self.dimensions.as_dvec2() });
responses.add(NodeGraphMessage::RunDocumentGraph);
responses.add(ViewportMessage::RepropagateUpdate);
responses.add(DeferMessage::AfterNavigationReady {
messages: vec![
DocumentMessage::ZoomCanvasToFitAll.into(),
DocumentMessage::DeselectAllLayers.into(),
PortfolioMessage::AutoSaveActiveDocument.into(),
],
});
}
responses.add(NodeGraphMessage::RunDocumentGraph);
responses.add(ViewportMessage::RepropagateUpdate);
responses.add(DeferMessage::AfterNavigationReady {
messages: vec![
DocumentMessage::ZoomCanvasToFitAll.into(),
DocumentMessage::DeselectAllLayers.into(),
PortfolioMessage::AutoSaveActiveDocument.into(),
],
});
responses.add(DocumentMessage::MarkAsSaved);
}
}