This commit is contained in:
Piotr Siuszko 2025-09-24 13:24:50 +02:00
parent ac277de75b
commit e4b8406e90
6 changed files with 154 additions and 68 deletions

49
.github/workflows/bundle.yml vendored Normal file
View File

@ -0,0 +1,49 @@
name: Bundle
on:
workflow_call:
inputs:
runningSystem:
description: "System to run the job on"
required: true
type: string
workflow_dispatch:
inputs:
runningSystem:
type: choice
description: "System to run the job on"
required: true
options:
- ubuntu-latest
- windows-latest
- macos-15
jobs:
build:
runs-on: ${{ inputs.runningSystem }}
steps:
- uses: actions/checkout@v5
- uses: actions/cache@v4
continue-on-error: false
with:
path: |
~/.cargo/bin/
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
~/.cache/sccache
target/
key: ${{ inputs.runningSystem }}-build-${{ hashFiles('**/Cargo.lock') }}
restore-keys: ${{ inputs.runningSystem }}-build-
- uses: extractions/setup-just@v2
- name: Install Cargo Bundle
run: cargo install --git https://github.com/Leinnan/cargo-bundler
- name: Bundle mac
if: inputs.bundle == true && startsWith(inputs.runningSystem, 'macos')
working-directory: crates/rpack_egui
run: |
just bundle
- uses: actions/upload-artifact@v4
with:
name: ${{ inputs.runningSystem }}
path: target/release/bundle

View File

@ -58,7 +58,8 @@ js-sys = "0.3"
[package.metadata.bundle] [package.metadata.bundle]
name = "Rpack" name = "Rpack"
resources = ["static/resources/*"] icon = ["static/base_icon.png"]
resources_mapping = [["static/JetBrains*","./"]]
identifier = "io.github.leinnan.rpack" identifier = "io.github.leinnan.rpack"
osx_url_schemes = ["io.github.leinnan.rpack"] osx_url_schemes = ["io.github.leinnan.rpack"]
short_description = "Tilemap Editor" short_description = "Tilemap Editor"

View File

@ -1,50 +1,15 @@
format_bundle := if os() == "windows" { "--format wxsmsi" } else { if os() == "linux" { "--format appimage" } else { "--format osx" } }
_gen_icon size postfix: # list available commands
sips -z {{size}} {{size}} static/base_icon.png --out static/icon.iconset/icon_{{postfix}}.png list:
cp static/icon.iconset/icon_{{postfix}}.png static/resources/{{postfix}}.png just --list
make_win_icon: make_win_icon:
convert static/base_icon.png -define icon:auto-resize=16,24,32,48,64,128,256,512 static/icon.ico convert static/base_icon.png -define icon:auto-resize=16,24,32,48,64,128,256,512 static/icon.ico
# Build the icon for macOS app create_mac_installer: bundle
make_mac_icon: pkgbuild --install-location /Applications --component ../../target/release/bundle/osx/Rpack.app ../../target/release/bundle/osx/Rpack.pkg
rm -rf static/icon.iconset
mkdir -p static/icon.iconset
rm -rf static/resources
mkdir -p static/resources
just _gen_icon 16 "16x16"
just _gen_icon 32 "32x32"
just _gen_icon 64 "64x64"
just _gen_icon 128 "128x128"
just _gen_icon 256 "256x256"
just _gen_icon 512 "512x512"
just _gen_icon 1024 "1024x1024"
just _gen_icon 32 "16x16@2x"
just _gen_icon 64 "32x32@2x"
just _gen_icon 128 "64x64@2x"
just _gen_icon 256 "128x128@2x"
just _gen_icon 512 "256x256@2x"
just _gen_icon 1024 "512x512@2x"
iconutil -c icns static/icon.iconset # Create the installer using cargo-bundler
rm -rf static/icon.iconset bundle:
mv static/icon.icns static/resources/icon.icns cargo bundler -r
# Build the macOS app
build_mac_app: make_mac_icon
rm -rf Rpack.app
cargo build --release
mkdir -p Rpack.app
mkdir -p Rpack.app/Contents
mkdir -p Rpack.app/Contents/MacOS
mkdir -p Rpack.app/Contents/Resources
cp static/Info.plist Rpack.app/Contents/Info.plist
cp static/resources/* Rpack.app/Contents/Resources/
cp ../../target/release/rpack_egui Rpack.app/Contents/MacOS/rpack_egui
create_mac_installer: build_mac_app
pkgbuild --install-location /Applications --component Rpack.app RpackInstaller.pkg
# Create the installer using cargo-bundle
create_installer:
cargo bundle --release

View File

@ -360,14 +360,14 @@ impl eframe::App for Application {
/// Called each time the UI needs repainting, which may be many times per second. /// Called each time the UI needs repainting, which may be many times per second.
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
{ {
#[cfg(feature = "profiler")] #[cfg(all(not(target_arch = "wasm32"), feature = "profiler"))]
puffin::profile_scope!("handle_undo"); puffin::profile_scope!("handle_undo");
self.undoer self.undoer
.feed_state(ctx.input(|input| input.time), &self.data); .feed_state(ctx.input(|input| input.time), &self.data);
} }
if !INPUT_QUEUE.is_empty() { if !INPUT_QUEUE.is_empty() {
let mut rebuild = false; let mut rebuild = false;
#[cfg(feature = "profiler")] #[cfg(all(not(target_arch = "wasm32"), feature = "profiler"))]
puffin::profile_scope!("loading_images"); puffin::profile_scope!("loading_images");
#[allow(dead_code)] #[allow(dead_code)]
@ -456,7 +456,7 @@ impl eframe::App for Application {
egui::TopBottomPanel::top("topPanel") egui::TopBottomPanel::top("topPanel")
.frame(egui::Frame::canvas(&ctx.style())) .frame(egui::Frame::canvas(&ctx.style()))
.show(ctx, |ui| { .show(ctx, |ui| {
#[cfg(feature = "profiler")] #[cfg(all(not(target_arch = "wasm32"), feature = "profiler"))]
puffin::profile_scope!("top_panel"); puffin::profile_scope!("top_panel");
// ui.add_space(TOP_SIDE_MARGIN); // ui.add_space(TOP_SIDE_MARGIN);
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))]
@ -505,7 +505,7 @@ impl eframe::App for Application {
// }); // });
}); });
ctx.input(|i| { ctx.input(|i| {
#[cfg(feature = "profiler")] #[cfg(all(not(target_arch = "wasm32"), feature = "profiler"))]
puffin::profile_scope!("dropped_files"); puffin::profile_scope!("dropped_files");
if i.raw.dropped_files.is_empty() { if i.raw.dropped_files.is_empty() {
return; return;
@ -555,7 +555,7 @@ impl eframe::App for Application {
egui::TopBottomPanel::bottom("bottom_panel") egui::TopBottomPanel::bottom("bottom_panel")
.frame(egui::Frame::canvas(&ctx.style())) .frame(egui::Frame::canvas(&ctx.style()))
.show(ctx, |ui| { .show(ctx, |ui| {
#[cfg(feature = "profiler")] #[cfg(all(not(target_arch = "wasm32"), feature = "profiler"))]
puffin::profile_scope!("bottom_panel"); puffin::profile_scope!("bottom_panel");
ui.add_space(5.0); ui.add_space(5.0);
ui.horizontal(|ui| { ui.horizontal(|ui| {
@ -592,7 +592,7 @@ impl eframe::App for Application {
.id_salt("rightPanel_scroll") .id_salt("rightPanel_scroll")
.show(ui, |ui| { .show(ui, |ui| {
ui.add_enabled_ui(!self.output.is_building(), |ui| { ui.add_enabled_ui(!self.output.is_building(), |ui| {
#[cfg(feature = "profiler")] #[cfg(all(not(target_arch = "wasm32"), feature = "profiler"))]
puffin::profile_scope!("right_panel"); puffin::profile_scope!("right_panel");
let mut changed = false; let mut changed = false;
Grid::new("settings_grid") Grid::new("settings_grid")
@ -686,7 +686,7 @@ impl eframe::App for Application {
ui.separator(); ui.separator();
{ {
ui.style_mut().interaction.selectable_labels = false; ui.style_mut().interaction.selectable_labels = false;
#[cfg(feature = "profiler")] #[cfg(all(not(target_arch = "wasm32"), feature = "profiler"))]
puffin::profile_scope!("image_list"); puffin::profile_scope!("image_list");
ui.horizontal(|ui| { ui.horizontal(|ui| {
@ -821,7 +821,7 @@ impl eframe::App for Application {
egui::CentralPanel::default() egui::CentralPanel::default()
.frame(Frame::central_panel(&ctx.style()).inner_margin(16i8)) .frame(Frame::central_panel(&ctx.style()).inner_margin(16i8))
.show(ctx, |ui| { .show(ctx, |ui| {
#[cfg(feature = "profiler")] #[cfg(all(not(target_arch = "wasm32"), feature = "profiler"))]
puffin::profile_scope!("central_panel"); puffin::profile_scope!("central_panel");
if let Some(error) = &self.last_error { if let Some(error) = &self.last_error {
let text = egui::RichText::new(format!("Error: {}", &error)) let text = egui::RichText::new(format!("Error: {}", &error))

View File

@ -1,3 +1,5 @@
use std::path::Path;
pub fn setup_custom_fonts(ctx: &egui::Context) { pub fn setup_custom_fonts(ctx: &egui::Context) {
// Start with the default fonts (we will be adding to them rather than replacing them). // Start with the default fonts (we will be adding to them rather than replacing them).
let mut fonts = egui::FontDefinitions::default(); let mut fonts = egui::FontDefinitions::default();
@ -41,26 +43,95 @@ pub fn setup_custom_fonts(ctx: &egui::Context) {
}); });
} }
#[cfg(not(windows))] #[cfg(target_arch = "wasm32")]
fn get_fonts() -> anyhow::Result<(Vec<u8>, Vec<u8>)> { fn get_fonts() -> anyhow::Result<(Vec<u8>, Vec<u8>)> {
use std::fs; let regular = include_bytes!("../static/JetBrainsMonoNL-Regular.ttf").to_vec();
let semibold = include_bytes!("../static/JetBrainsMono-SemiBold.ttf").to_vec();
let font_path = std::path::Path::new("/System/Library/Fonts");
let regular = fs::read(font_path.join("SFNSRounded.ttf"))?;
let semibold = fs::read(font_path.join("SFCompact.ttf"))?;
Ok((regular, semibold)) Ok((regular, semibold))
} }
#[cfg(windows)] #[cfg(not(target_arch = "wasm32"))]
fn get_fonts() -> anyhow::Result<(Vec<u8>, Vec<u8>)> { fn get_fonts() -> anyhow::Result<(Vec<u8>, Vec<u8>)> {
use std::fs; let Some(regular) =
let app_data = std::env::var("APPDATA")?; try_get_font_from_list(&["JetBrainsMonoNL-Regular", "SFNSRounded", "aptos"])
let font_path = std::path::Path::new(&app_data); else {
anyhow::bail!("Failed to find a suitable font");
let regular = fs::read(font_path.join("../Local/Microsoft/Windows/Fonts/aptos.ttf"))?; };
let semibold = fs::read(font_path.join("../Local/Microsoft/Windows/Fonts/aptos-semibold.ttf"))?; let Some(semibold) =
try_get_font_from_list(&["JetBrainsMono-SemiBold", "SFNSRounded", "aptos-semibold"])
else {
anyhow::bail!("Failed to find a suitable font");
};
Ok((regular, semibold)) Ok((regular, semibold))
} }
fn try_get_font_from_list(font_names: &[&str]) -> Option<Vec<u8>> {
for font_name in font_names {
if let Some(font) = try_get_font(font_name) {
return Some(font);
}
}
None
}
fn try_get_font(font_name: &str) -> Option<Vec<u8>> {
for dir in font_dirs() {
if let Ok(font) = std::fs::read(Path::new(&dir).join(format!("{}.ttf", font_name))) {
return Some(font);
}
if let Ok(font) = std::fs::read(Path::new(&dir).join(format!("{}.otf", font_name))) {
return Some(font);
}
}
None
}
fn font_dirs() -> Vec<String> {
let mut dirs = Vec::new();
#[cfg(target_os = "linux")]
{
dirs.push("/usr/share/fonts".into());
dirs.push("/usr/share/fonts/truetype".into());
}
#[cfg(unix)]
{
use std::{path::PathBuf, str::FromStr};
#[cfg(target_os = "macos")]
{
dirs.push("/System/Library/Fonts".into());
if let Some(resources_font_dir) = std::env::current_exe().ok().and_then(|p| {
p.ancestors()
.nth(2)
.map(|p| p.join("Resources/fonts").to_string_lossy().into_owned())
}) {
eprintln!("{}", &resources_font_dir);
dirs.push(resources_font_dir);
}
}
if let Some(home) =
std::env::var_os("HOME").and_then(|s| PathBuf::from_str(&s.to_string_lossy()).ok())
{
#[cfg(target_os = "macos")]
{
dirs.push(format!("{}/Library/Fonts", home.display()));
}
#[cfg(target_os = "linux")]
{
dirs.push(format!("{}/.local/share/fonts", home.display()));
}
}
}
#[cfg(target_os = "windows")]
{
if let Ok(dir) = std::env::var("APPDATA") {
let font_path = std::path::Path::new(&dir).join("../Local/Microsoft/Windows/Fonts/");
dirs.push(font_path.display().to_string());
}
}
dirs
}

View File

@ -30,7 +30,7 @@ fn start_puffin_server() {
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))]
fn main() -> eframe::Result<()> { fn main() -> eframe::Result<()> {
env_logger::init(); // Log to stderr (if you run with `RUST_LOG=debug`). env_logger::init(); // Log to stderr (if you run with `RUST_LOG=debug`).
#[cfg(feature = "profiler")] #[cfg(all(not(target_arch = "wasm32"), feature = "profiler"))]
start_puffin_server(); start_puffin_server();
let file_arg: Option<String> = if std::env::args().len() > 1 { let file_arg: Option<String> = if std::env::args().len() > 1 {
std::env::args().skip(1).next() std::env::args().skip(1).next()