FEAT: Launch with custom config location

FEAT: Generate banner
FEAT: Start blender with banner
This commit is contained in:
valerio
2026-03-10 22:14:27 +01:00
parent 132b1ccee7
commit b9b6bd1490
11 changed files with 224 additions and 30 deletions
+10 -4
View File
@@ -1,6 +1,10 @@
<script>
import { deleteVersion, launchBlenderVersion } from '$lib/library';
import { toggleFavourite } from '$lib/library';
import {
deleteVersion,
launchBlenderVersion,
toggleFavourite,
registerVersion
} from '$lib/library';
import Button from '$lib/components/Button.svelte';
import Menu from '$lib/components/Menu.svelte';
let { version } = $props();
@@ -44,8 +48,10 @@
<button
type="button"
class="menu-item"
onclick={close}
onkeydown={(e) => (e.key === 'Enter' || e.key === ' ') && close()}>Option 3</button
onclick={() => registerVersion(version) && close()}
onkeydown={(e) =>
(e.key === 'Enter' || e.key === ' ') && registerVersion(version) && close()}
>Default</button
>
{/snippet}
</Menu>
+71 -18
View File
@@ -7,7 +7,11 @@ import {
import { stat, mkdir, readDir, copyFile, rename, remove } from '@tauri-apps/plugin-fs';
import { open } from '@tauri-apps/plugin-dialog';
import baseicon from '$lib/assets/baseicon.png';
import europa from '$lib/assets/fonts/Europa-Mono-Medium.otf';
import { BASE_LIBRARY_DIR } from './settings';
import figlet from 'figlet';
import shadow from 'figlet/fonts/Classy';
figlet.parseFont('Big', shadow);
/**
* Create a custom icon with text overlay
@@ -19,45 +23,32 @@ export async function createIcon(iconText) {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
// Load the base icon image
const img = new Image();
img.src = baseicon;
// Wait for image to load
await new Promise((resolve) => {
img.onload = resolve;
});
// Set canvas dimensions to match image
canvas.width = img.width;
canvas.height = img.height;
// Draw the base icon
ctx.drawImage(img, 0, 0);
// Set text properties
ctx.fillStyle = '#7d70ba';
ctx.textAlign = 'left';
ctx.textBaseline = 'top';
// Load the custom font
const fontFace = new FontFace(
'Europa-Mono',
`url(${baseicon.replace('baseicon.png', 'fonts/Europa-Mono-Medium.otf')})`
);
const fontFace = new FontFace('Europa-Mono', `url(${europa})`);
await fontFace.load();
document.fonts.add(fontFace);
// Set font size based on canvas dimensions
const fontSize = canvas.width / 6;
ctx.font = `${fontSize}px Europa-Mono`;
// Set font size based on canvas dimensions
// Add version text in the center
const text = iconText || 'XXX';
ctx.fillText(text, 150, 150);
// Convert canvas to blob
return new Promise((resolve) => {
canvas.toBlob((blob) => {
resolve(blob);
@@ -65,6 +56,71 @@ export async function createIcon(iconText) {
});
}
/**
* Create a custom banner with version number next to the baseIcon image
* @param {string} versionText - Version text to display
* @returns {Promise<Blob>} Promise resolving to a PNG blob of the banner
*/
export async function createBanner(versionText) {
// Create canvas element for banner
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
// Set banner dimensions
canvas.width = 600;
canvas.height = 90;
// Draw background
ctx.fillStyle = '#171a21';
ctx.fillRect(0, 0, canvas.width, canvas.height);
// Load and draw base icon
const img = new Image();
img.src = baseicon;
await new Promise((resolve) => {
img.onload = resolve;
});
// Calculate icon position (centered vertically, left-aligned)
const iconSize = 70;
const iconX = 20;
const iconY = (canvas.height - iconSize) / 2;
// Add version text
ctx.fillStyle = '#f5f5f5';
ctx.textAlign = 'left';
ctx.textBaseline = 'middle';
const fontFace = new FontFace('Europa-Mono', `url(${europa})`);
await fontFace.load();
document.fonts.add(fontFace);
const fontSize = 10;
ctx.font = `${fontSize}px Europa-Mono`;
// Position text to the right of the icon
const textX = 20;
const textY = canvas.height / 2;
const bannerText = await figlet.text(versionText + ' - Base', { font: 'Big' });
const lines = bannerText.split('\n');
const lineHeight = fontSize * 1.2;
const startY = textY - (lines.length * lineHeight) / 2 + lineHeight / 2;
ctx.font = `${fontSize}px monospace`;
ctx.textAlign = 'left';
ctx.textBaseline = 'top';
lines.forEach((line, index) => {
ctx.fillText(line, textX, startY + index * lineHeight);
});
return new Promise((resolve) => {
canvas.toBlob((blob) => {
resolve(blob);
}, 'image/png');
});
}
/**
* Create library directory structure if it doesn't exist
* @param {string} libraryDir - Library directory path
@@ -72,13 +128,10 @@ export async function createIcon(iconText) {
*/
export async function ensureLibraryStructure(libraryDir) {
try {
// Create main library directory
const baseDir = await join(libraryDir, BASE_LIBRARY_DIR);
// Create blender subdirectory
const blenderDir = await join(baseDir, 'blender');
await mkdir(blenderDir, { recursive: true });
// Create templates subdirectory
const templatesDir = await join(baseDir, 'templates');
await mkdir(templatesDir, { recursive: true });
@@ -99,7 +152,7 @@ export async function ensureLibraryStructure(libraryDir) {
*/
export async function selectDirectory(currentPath) {
try {
// Use currentPath if provided, otherwise use app data directory
// Use currentPath if proided, otherwise use app data directory
let defaultPath = await appDataDir();
if (currentPath) {
defaultPath = currentPath;
+74 -5
View File
@@ -1,5 +1,5 @@
import { currentSettings } from '$lib/settings.js';
import { createIcon } from './file_utils';
import { createIcon, createBanner } from './file_utils';
import { platform } from '@tauri-apps/plugin-os';
import { join, localDataDir } from '@tauri-apps/api/path';
import { exists, stat, readDir, writeFile, remove } from '@tauri-apps/plugin-fs';
@@ -22,6 +22,20 @@ import { updateDownloadProgress } from './download';
export const currentInstalledVersions = writable([]);
const favouritesStore = new LazyStore('versions.json');
export async function generateVersionBanner(version) {
const bannerPath = `${version.path}/banner.png`;
if (await exists(bannerPath)) {
return;
}
try {
const banner = await createBanner(version.version);
const bannerBuffer = new Uint8Array(await banner.arrayBuffer());
await writeFile(bannerPath, bannerBuffer);
} catch (error) {
console.error(`Failed to generate banner for version: ${version.version}`, error);
}
}
/**
* Create icon and write it to disk
* @param {blenderVersion} version
@@ -39,7 +53,6 @@ export async function generateVersionIcon(version) {
}
try {
const newIcon = await createIcon(version.version);
const iconPath = `${version.path}/icon.png`;
const iconBuffer = new Uint8Array(await newIcon.arrayBuffer());
await writeFile(iconPath, iconBuffer);
} catch (error) {
@@ -286,6 +299,7 @@ export async function getInstalledVersions() {
//check if version has an icon, if not generate one
await generateVersionIcon(newVersion);
await generateVersionBanner(newVersion);
// create desktop file if autoCreateShortcuts is enabled
if (settings.autoCreateShortcuts) {
await createDesktopFile(newVersion);
@@ -314,11 +328,11 @@ export async function getInstalledVersions() {
}
/**
* Launch a specific Blender version.
* Register a Blender version with the system.
* @param {blenderVersion} version - The version object to launch
* @returns {Promise<{success: boolean, message: string}>} Result of launch attempt
*/
export async function launchBlenderVersion(version) {
export async function registerVersion(version) {
try {
if (!version || !version.executable) {
return {
@@ -338,7 +352,62 @@ export async function launchBlenderVersion(version) {
};
}
// Launch the executable
invoke('launch_binary', { path: version.executable, args: [] });
invoke('launch_binary', { path: version.executable, args: ['--register'] });
} catch (error) {
console.error('Error registering Blender:', error);
const errorMessage = error instanceof Error ? error.message : String(error);
return {
success: false,
message: `Registration failed: ${errorMessage}`
};
}
}
/**
* Launch a specific Blender version.
* @param {blenderVersion} version - The version object to launch
* @returns {Promise<{success: boolean, message: string}>} Result of launch attempt
*/
export async function launchBlenderVersion(version, template = null) {
try {
if (!version || !version.executable) {
return {
success: false,
message: 'Invalid version or executable not found'
};
}
// Check if executable exists
try {
await stat(version.executable);
} catch (error) {
console.error(error);
return {
success: false,
message: `Executable not found: ${version.executable}`
};
}
const settings = get(currentSettings);
const libraryDir = settings.libraryDir;
if (!libraryDir) {
return {
success: false,
message: `couldn't find library: ${version.executable}`
};
}
const blenderConfigPath = await join(libraryDir, BASE_LIBRARY_DIR, 'config', version.version);
// Launch the executable
invoke('launch_binary', {
path: version.executable,
args: [],
env: {
BLENDER_USER_RESOURCES: blenderConfigPath,
BLENDER_CUSTOM_SPLASH_BANNER: await join(version.path, 'banner.png')
}
});
} catch (error) {
console.error('Error launching Blender:', error);
const errorMessage = error instanceof Error ? error.message : String(error);