fix(splash): skip runtime title/version draw and use mantle background
Some checks failed
Build and Test / build (pull_request) Failing after 19m15s

The splash PNG already has the title and version baked in by
generate-splash.py, but SplashScreen::splashImage() was drawing them
again at position (6,75) which fell outside the rounded background
rectangle (starts at 30,30). Skip runtime text rendering for Kindred
Create while keeping the dev build warning.

Also switch the splash background from Catppuccin Mocha base (#1e1e2e)
to mantle (#181825) for a darker appearance and regenerate assets.
This commit is contained in:
2026-02-12 10:08:23 -06:00
parent 70118201b0
commit f71decca08
4 changed files with 69 additions and 52 deletions

View File

@@ -34,53 +34,67 @@ except ImportError:
# Catppuccin Mocha colors # Catppuccin Mocha colors
COLORS = { COLORS = {
'base': '#1e1e2e', "mantle": "#181825",
'surface0': '#313244', "base": "#1e1e2e",
'text': '#cdd6f4', "surface0": "#313244",
'subtext0': '#a6adc8', "text": "#cdd6f4",
'blue': '#89b4fa', "subtext0": "#a6adc8",
'lavender': '#b4befe', "blue": "#89b4fa",
"lavender": "#b4befe",
} }
def hex_to_rgb(hex_color): def hex_to_rgb(hex_color):
"""Convert hex color to RGB tuple.""" """Convert hex color to RGB tuple."""
hex_color = hex_color.lstrip('#') hex_color = hex_color.lstrip("#")
return tuple(int(hex_color[i:i+2], 16) for i in (0, 2, 4)) return tuple(int(hex_color[i : i + 2], 16) for i in (0, 2, 4))
def create_rounded_rectangle(draw, bbox, radius, fill): def create_rounded_rectangle(draw, bbox, radius, fill):
"""Draw a rounded rectangle.""" """Draw a rounded rectangle."""
x1, y1, x2, y2 = bbox x1, y1, x2, y2 = bbox
draw.rounded_rectangle(bbox, radius=radius, fill=fill) draw.rounded_rectangle(bbox, radius=radius, fill=fill)
def load_svg_as_image(svg_path, width, height): def load_svg_as_image(svg_path, width, height):
"""Load an SVG file and convert to PIL Image at specified size.""" """Load an SVG file and convert to PIL Image at specified size."""
if cairosvg: if cairosvg:
import io import io
png_data = cairosvg.svg2png(url=str(svg_path), output_width=width, output_height=height) png_data = cairosvg.svg2png(url=str(svg_path), output_width=width, output_height=height)
return Image.open(io.BytesIO(png_data)).convert('RGBA') return Image.open(io.BytesIO(png_data)).convert("RGBA")
else: else:
# Fallback: try using inkscape command line # Fallback: try using inkscape command line
import subprocess import subprocess
import tempfile import tempfile
with tempfile.NamedTemporaryFile(suffix='.png', delete=False) as tmp:
with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as tmp:
tmp_path = tmp.name tmp_path = tmp.name
try: try:
subprocess.run([ subprocess.run(
'inkscape', '-w', str(width), '-h', str(height), ["inkscape", "-w", str(width), "-h", str(height), str(svg_path), "-o", tmp_path],
str(svg_path), '-o', tmp_path check=True,
], check=True, capture_output=True) capture_output=True,
img = Image.open(tmp_path).convert('RGBA') )
img = Image.open(tmp_path).convert("RGBA")
return img return img
finally: finally:
if os.path.exists(tmp_path): if os.path.exists(tmp_path):
os.unlink(tmp_path) os.unlink(tmp_path)
def get_font(size, bold=False): def get_font(size, bold=False):
"""Get a font, falling back to default if not available.""" """Get a font, falling back to default if not available."""
font_names = [ font_names = [
'/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf' if bold else '/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf', "/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf"
'/usr/share/fonts/truetype/liberation/LiberationSans-Bold.ttf' if bold else '/usr/share/fonts/truetype/liberation/LiberationSans-Regular.ttf', if bold
'/usr/share/fonts/TTF/DejaVuSans-Bold.ttf' if bold else '/usr/share/fonts/TTF/DejaVuSans.ttf', else "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf",
"/usr/share/fonts/truetype/liberation/LiberationSans-Bold.ttf"
if bold
else "/usr/share/fonts/truetype/liberation/LiberationSans-Regular.ttf",
"/usr/share/fonts/TTF/DejaVuSans-Bold.ttf"
if bold
else "/usr/share/fonts/TTF/DejaVuSans.ttf",
] ]
for font_name in font_names: for font_name in font_names:
if os.path.exists(font_name): if os.path.exists(font_name):
@@ -88,6 +102,7 @@ def get_font(size, bold=False):
# Fallback to default # Fallback to default
return ImageFont.load_default() return ImageFont.load_default()
def create_splash(output_path, logo_path, width, height, version, freecad_version, scale=1): def create_splash(output_path, logo_path, width, height, version, freecad_version, scale=1):
"""Create a splash screen image.""" """Create a splash screen image."""
# Scale dimensions # Scale dimensions
@@ -97,11 +112,11 @@ def create_splash(output_path, logo_path, width, height, version, freecad_versio
padding = int(30 * scale) padding = int(30 * scale)
# Create image with transparent background # Create image with transparent background
img = Image.new('RGBA', (w, h), (0, 0, 0, 0)) img = Image.new("RGBA", (w, h), (0, 0, 0, 0))
draw = ImageDraw.Draw(img) draw = ImageDraw.Draw(img)
# Draw rounded rectangle background # Draw rounded rectangle background
bg_color = hex_to_rgb(COLORS['base']) bg_color = hex_to_rgb(COLORS["mantle"])
create_rounded_rectangle(draw, (padding, padding, w - padding, h - padding), radius, bg_color) create_rounded_rectangle(draw, (padding, padding, w - padding, h - padding), radius, bg_color)
# Load and place logo # Load and place logo
@@ -125,7 +140,7 @@ def create_splash(output_path, logo_path, width, height, version, freecad_versio
title_width = title_bbox[2] - title_bbox[0] title_width = title_bbox[2] - title_bbox[0]
title_x = (w - title_width) // 2 title_x = (w - title_width) // 2
title_y = int(250 * scale) title_y = int(250 * scale)
draw.text((title_x, title_y), title, fill=hex_to_rgb(COLORS['text']), font=title_font) draw.text((title_x, title_y), title, fill=hex_to_rgb(COLORS["text"]), font=title_font)
# Draw version string # Draw version string
version_font = get_font(int(12 * scale)) version_font = get_font(int(12 * scale))
@@ -134,12 +149,15 @@ def create_splash(output_path, logo_path, width, height, version, freecad_versio
version_width = version_bbox[2] - version_bbox[0] version_width = version_bbox[2] - version_bbox[0]
version_x = (w - version_width) // 2 version_x = (w - version_width) // 2
version_y = title_y + int(35 * scale) version_y = title_y + int(35 * scale)
draw.text((version_x, version_y), version_str, fill=hex_to_rgb(COLORS['subtext0']), font=version_font) draw.text(
(version_x, version_y), version_str, fill=hex_to_rgb(COLORS["subtext0"]), font=version_font
)
# Save # Save
img.save(output_path, 'PNG') img.save(output_path, "PNG")
print(f"Created: {output_path}") print(f"Created: {output_path}")
def create_about(output_path, logo_path, width, height, scale=1): def create_about(output_path, logo_path, width, height, scale=1):
"""Create an about dialog image.""" """Create an about dialog image."""
# Scale dimensions # Scale dimensions
@@ -147,7 +165,7 @@ def create_about(output_path, logo_path, width, height, scale=1):
h = int(height * scale) h = int(height * scale)
# Create image # Create image
img = Image.new('RGBA', (w, h), hex_to_rgb(COLORS['base']) + (255,)) img = Image.new("RGBA", (w, h), hex_to_rgb(COLORS["base"]) + (255,))
draw = ImageDraw.Draw(img) draw = ImageDraw.Draw(img)
# Load and place logo # Load and place logo
@@ -163,18 +181,19 @@ def create_about(output_path, logo_path, width, height, scale=1):
print(f"Warning: Could not load logo: {e}") print(f"Warning: Could not load logo: {e}")
# Save # Save
img.save(output_path, 'PNG') img.save(output_path, "PNG")
print(f"Created: {output_path}") print(f"Created: {output_path}")
def main(): def main():
parser = argparse.ArgumentParser(description='Generate Kindred Create splash screens') parser = argparse.ArgumentParser(description="Generate Kindred Create splash screens")
parser.add_argument('--version', default='0.1.0', help='Kindred Create version') parser.add_argument("--version", default="0.1.0", help="Kindred Create version")
parser.add_argument('--freecad-version', default='1.0.0', help='FreeCAD version') parser.add_argument("--freecad-version", default="1.0.0", help="FreeCAD version")
args = parser.parse_args() args = parser.parse_args()
script_dir = Path(__file__).parent script_dir = Path(__file__).parent
logo_path = script_dir / 'kindred-logo.svg' logo_path = script_dir / "kindred-logo.svg"
icons_dir = script_dir.parent.parent / 'src' / 'Gui' / 'Icons' icons_dir = script_dir.parent.parent / "src" / "Gui" / "Icons"
if not logo_path.exists(): if not logo_path.exists():
print(f"Error: Logo not found at {logo_path}") print(f"Error: Logo not found at {logo_path}")
@@ -182,33 +201,31 @@ def main():
# Create splash screens (600x400 as per spec) # Create splash screens (600x400 as per spec)
create_splash( create_splash(
icons_dir / 'kindredcreatesplash.png', icons_dir / "kindredcreatesplash.png",
logo_path, logo_path,
600, 400, 600,
400,
args.version, args.version,
args.freecad_version, args.freecad_version,
scale=1 scale=1,
) )
# Create 2x version for HiDPI # Create 2x version for HiDPI
create_splash( create_splash(
icons_dir / 'kindredcreatesplash_2x.png', icons_dir / "kindredcreatesplash_2x.png",
logo_path, logo_path,
600, 400, 600,
400,
args.version, args.version,
args.freecad_version, args.freecad_version,
scale=2 scale=2,
) )
# Create about image # Create about image
create_about( create_about(icons_dir / "kindredcreateabout.png", logo_path, 400, 200, scale=1)
icons_dir / 'kindredcreateabout.png',
logo_path,
400, 200,
scale=1
)
print("\nSplash screen generation complete!") print("\nSplash screen generation complete!")
if __name__ == '__main__':
if __name__ == "__main__":
main() main()

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 24 KiB

View File

@@ -360,9 +360,6 @@ QPixmap SplashScreen::splashImage()
fontExe.setPointSizeF(20.0); fontExe.setPointSizeF(20.0);
QFontMetrics metricExe(fontExe); QFontMetrics metricExe(fontExe);
int l = QtTools::horizontalAdvance(metricExe, title); int l = QtTools::horizontalAdvance(metricExe, title);
if (title == QLatin1String("Kindred Create")) {
// For Kindred Create splash, we draw the title as part of the dynamic rendering
}
int w = splash_image.width(); int w = splash_image.width();
int h = splash_image.height(); int h = splash_image.height();
@@ -386,13 +383,16 @@ QPixmap SplashScreen::splashImage()
QColor color(QString::fromStdString(tc->second)); QColor color(QString::fromStdString(tc->second));
if (color.isValid()) { if (color.isValid()) {
painter.setPen(color); painter.setPen(color);
painter.setFont(fontExe); if (title != QLatin1String("Kindred Create")) {
if (title != QLatin1String("FreeCAD")) { // Kindred Create's splash PNG already contains the title and version
// FreeCAD's Splashscreen already contains the EXE name, no need to draw it painter.setFont(fontExe);
painter.drawText(x, y, title); if (title != QLatin1String("FreeCAD")) {
// FreeCAD's Splashscreen already contains the EXE name, no need to draw it
painter.drawText(x, y, title);
}
painter.setFont(fontVer);
painter.drawText(x + (l + 235), y - 7, version);
} }
painter.setFont(fontVer);
painter.drawText(x + (l + 235), y - 7, version);
QColor warningColor(QString::fromStdString(wc->second)); QColor warningColor(QString::fromStdString(wc->second));
if (suffix == QLatin1String("dev") && warningColor.isValid()) { if (suffix == QLatin1String("dev") && warningColor.isValid()) {
fontVer.setPointSizeF(14.0); fontVer.setPointSizeF(14.0);