Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions .github/macos/build_create-dmg.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#!/bin/sh
set -e

version="1.2.2"

prefix="$PWD/lib"
mkdir -p "$prefix"
cd "$prefix"

[ -f "create-dmg-$version.tar.gz" ] || \
curl -Lo "create-dmg-$version.tar.gz" "https://github.com/create-dmg/create-dmg/archive/refs/tags/v$version.tar.gz"
rm -rf "create-dmg-$version"
tar xf "create-dmg-$version.tar.gz"
cd "create-dmg-$version"
make prefix="$prefix" install
cd ..
rm -rf "create-dmg-$version"
26 changes: 26 additions & 0 deletions .github/macos/build_fltk.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
#!/bin/sh
set -e

version="1.4.4"

NPROC="${NPROC:-$(getconf _NPROCESSORS_ONLN)}"
export MAKEFLAGS="-j$NPROC"

prefix="$PWD/lib"
mkdir -p "$prefix"
cd "$prefix"

[ -f "fltk-$version-source.tar.bz2" ] || \
curl -LO "https://github.com/fltk/fltk/releases/download/release-$version/fltk-$version-source.tar.bz2"
rm -rf "fltk-$version"
tar xf "fltk-$version-source.tar.bz2"
cd "fltk-$version"
cmake \
-D CMAKE_INSTALL_PREFIX="$(realpath "$PWD/../..")" \
-D CMAKE_BUILD_TYPE=Release \
-D FLTK_USE_SYSTEM_LIBPNG=0 \
-D FLTK_USE_SYSTEM_ZLIB=0 \
-D CMAKE_OSX_DEPLOYMENT_TARGET="$(sw_vers -productVersion | cut -d '.' -f 1).0"
make DIRS='$(IMAGEDIRS) src $(CAIRODIR)' install
cd ..
rm -rf "fltk-$version"
37 changes: 37 additions & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ name: CI
on:
push:
branches: [ master ]
tags: "*"
pull_request:

jobs:
Expand Down Expand Up @@ -34,3 +35,39 @@ jobs:
- name: Build
run: |
make

build-mac:
runs-on: macos-13
env:
MACOSX_DEPLOYMENT_TARGET: "10.13"
CFLAGS: -O3 -pipe -flto
CXXFLAGS: -O3 -pipe -flto
steps:
- name: Checkout
uses: actions/checkout@master

- name: Install FLTK
run: |
.github/macos/build_fltk.sh

- name: Build
run: |
make -j$(getconf _NPROCESSORS_ONLN)

- name: Install create-dmg
if: startsWith(github.ref, 'refs/tags/')
run: |
.github/macos/build_create-dmg.sh

- name: Create DMG
if: startsWith(github.ref, 'refs/tags/')
run: |
export PATH="$PWD/lib/bin:$PATH"
make appdmg

- name: Upload artifacts
if: startsWith(github.ref, 'refs/tags/')
uses: actions/upload-artifact@v4
with:
name: "Tilemap Studio.dmg"
path: "bin/Tilemap Studio.dmg"
83 changes: 79 additions & 4 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,10 +1,20 @@
OS_MAC :=
ifeq ($(shell uname -s),Darwin)
OS_MAC := 1
endif

DESTDIR =
PREFIX = /usr/local

APPNAME = Tilemap Studio
tilemapstudio = tilemapstudio
tilemapstudiod = tilemapstudiod

ifdef OS_MAC
CXX ?= clang++
else
CXX ?= g++
endif
LD = $(CXX)
RM = rm -rf

Expand All @@ -17,7 +27,10 @@ bindir = bin
fltk-config = $(bindir)/fltk-config

CXXFLAGS := -std=c++17 -I$(srcdir) -I$(resdir) $(shell $(fltk-config) --use-images --cxxflags) $(CXXFLAGS)
LDFLAGS := $(shell $(fltk-config) --use-images --ldstaticflags) $(shell pkg-config --libs xpm) $(LDFLAGS)
LDFLAGS := $(shell $(fltk-config) --use-images --ldstaticflags) $(LDFLAGS)
ifndef OS_MAC
LDFLAGS += $(shell pkg-config --libs xpm)
endif

RELEASEFLAGS = -DNDEBUG -O3 -flto
DEBUGFLAGS = -DDEBUG -D_DEBUG -O0 -g -ggdb3 -Wall -Wextra -pedantic -Wno-unknown-pragmas -Wno-sign-compare -Wno-unused-parameter
Expand All @@ -26,11 +39,17 @@ COMMON = $(wildcard $(srcdir)/*.h) $(wildcard $(resdir)/*.xpm) $(resdir)/help.ht
SOURCES = $(wildcard $(srcdir)/*.cpp)
OBJECTS = $(SOURCES:$(srcdir)/%.cpp=$(tmpdir)/%.o)
DEBUGOBJECTS = $(SOURCES:$(srcdir)/%.cpp=$(debugdir)/%.o)

ifdef OS_MAC
SOURCES_MAC = $(wildcard $(srcdir)/*.mm)
OBJECTS += $(SOURCES_MAC:$(srcdir)/%.mm=$(tmpdir)/%.o)
DEBUGOBJECTS += $(SOURCES_MAC:$(srcdir)/%.mm=$(debugdir)/%.o)
endif

TARGET = $(bindir)/$(tilemapstudio)
DEBUGTARGET = $(bindir)/$(tilemapstudiod)
DESKTOP = "$(DESTDIR)$(PREFIX)/share/applications/Tilemap Studio.desktop"

.PHONY: all $(tilemapstudio) $(tilemapstudiod) release debug clean install uninstall
.PHONY: all $(tilemapstudio) $(tilemapstudiod) release debug clean appdir appdmg install uninstall

.SUFFIXES: .o .cpp

Expand Down Expand Up @@ -61,9 +80,63 @@ $(debugdir)/%.o: $(srcdir)/%.cpp $(COMMON)
@mkdir -p $(@D)
$(CXX) -c $(CXXFLAGS) -o $@ $<

ifdef OS_MAC
$(tmpdir)/%.o: $(srcdir)/%.mm $(COMMON)
@mkdir -p $(@D)
$(CXX) -c $(CXXFLAGS) -o $@ $<

$(debugdir)/%.o: $(srcdir)/%.mm $(COMMON)
@mkdir -p $(@D)
$(CXX) -c $(CXXFLAGS) -o $@ $<
endif

clean:
$(RM) $(TARGET) $(DEBUGTARGET) $(OBJECTS) $(DEBUGOBJECTS)

ifdef OS_MAC

APPDIR = "$(bindir)/$(APPNAME).app"
APPDMG = "$(bindir)/$(APPNAME).dmg"
CONTENTS = $(APPDIR)/Contents

appdir: release
rm -rf $(APPDIR)
install -d $(CONTENTS)/macOS $(CONTENTS)/Resources
install -m755 $(TARGET) $(CONTENTS)/macOS/tilemapstudio
install -m644 $(resdir)/app.icns $(CONTENTS)/Resources/AppIcon.icns
install -m644 $(resdir)/Info.plist $(CONTENTS)/Info.plist
printf 'APPL????' > $(CONTENTS)/PkgInfo

appdmg: appdir
rm -f $(APPDMG)
rm -rf $(APPDMG).dir/
mkdir -p $(APPDMG).dir
cp -a $(APPDIR) $(APPDMG).dir/
create-dmg \
--volname "$(APPNAME)" \
--volicon $(resdir)/app.icns \
--window-pos 200 120 \
--window-size 800 400 \
--icon-size 100 \
--icon "$(APPNAME).app" 200 190 \
--hide-extension "$(APPNAME).app" \
--app-drop-link 600 185 \
$(APPDMG) $(APPDMG).dir/
rm -rf $(APPDMG).dir/

install: appdir
rm -rf "/Applications/$(APPNAME).app"
cp -av $(APPDIR) "/Applications/$(APPNAME).app"
# Remove admin-owned files if ran as "sudo"
rm -rf $(APPDIR)

uninstall:
rm -rf "/Applications/$(APPNAME).app"

else # not OS_MAC

DESKTOP = "$(DESTDIR)$(PREFIX)/share/applications/$(APPNAME).desktop"

install: release
mkdir -p $(DESTDIR)$(PREFIX)/bin
cp $(TARGET) $(DESTDIR)$(PREFIX)/bin/$(tilemapstudio)
Expand All @@ -72,7 +145,7 @@ install: release
cp $(resdir)/app-icon.xpm $(DESTDIR)$(PREFIX)/share/pixmaps/tilemapstudio16.xpm
mkdir -p $(DESTDIR)$(PREFIX)/share/applications
echo "[Desktop Entry]" > $(DESKTOP)
echo "Name=Tilemap Studio" >> $(DESKTOP)
echo "Name=$(APPNAME)" >> $(DESKTOP)
echo "Comment=Edit Game Boy, Color, and Advance tilemaps" >> $(DESKTOP)
echo "Icon=$(PREFIX)/share/pixmaps/tilemapstudio48.xpm" >> $(DESKTOP)
echo "Exec=$(PREFIX)/bin/$(tilemapstudio)" >> $(DESKTOP)
Expand All @@ -84,3 +157,5 @@ uninstall:
rm -f $(DESTDIR)$(PREFIX)/share/pixmaps/tilemapstudio48.xpm
rm -f $(DESTDIR)$(PREFIX)/share/pixmaps/tilemapstudio16.xpm
rm -f $(DESKTOP)

endif # OS_MAC
82 changes: 82 additions & 0 deletions res/Info.plist
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>tilemapstudio</string>
<key>CFBundleIconFile</key>
<string>AppIcon</string>
<key>CFBundleIdentifier</key>
<string>rangi42.tilemapstudio</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>Tilemap Studio</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>4.0.1</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>LSMinimumSystemVersion</key>
<string>10.13</string>
<key>NSHumanReadableCopyright</key>
<string></string>
<key>UTExportedTypeDeclarations</key>
<array>
<dict>
<key>UTTypeConformsTo</key>
<array>
<string>public.data</string>
</array>
<key>UTTypeDescription</key>
<string>Tilemap Studio Tilemap</string>
<key>UTTypeIdentifier</key>
<string>rangi42.tilemapstudio.tilemap</string>
<key>UTTypeTagSpecification</key>
<dict>
<key>public.filename-extension</key>
<array>
<string>tilemap</string>
<string>rle</string>
</array>
</dict>
</dict>
</array>
<key>CFBundleDocumentTypes</key>
<array>
<dict>
<key>LSItemContentTypes</key>
<array>
<string>rangi42.tilemapstudio.tilemap</string>
</array>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>LSHandlerRank</key>
<string>Default</string>
</dict>
<dict>
<key>LSItemContentTypes</key>
<array>
<string>public.data</string>
</array>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>LSHandlerRank</key>
<string>Alternate</string>
</dict>
<dict>
<key>LSItemContentTypes</key>
<array>
<string>public.png</string>
</array>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>LSHandlerRank</key>
<string>Alternate</string>
</dict>
</array>
</dict>
</plist>
Binary file added res/app.icns
Binary file not shown.
14 changes: 7 additions & 7 deletions res/help.html
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ <h1>)" PROGRAM_NAME R"(</h1>
<li><b>Initial:</b> Double-click a .tilemap file in File Explorer. (Run install.bat to associate .tilemap files with )" PROGRAM_NAME R"(.)</li>
<li><b>Menu bar:</b> Run )" PROGRAM_EXE R"( and select the Tilemap&nbsp;→&nbsp;Open… menu item.</li>
<li><b>Toolbar:</b> Run )" PROGRAM_EXE R"( and click the Open toolbar button.</li>
<li><b>Keyboard:</b> Run )" PROGRAM_EXE R"( and press Ctrl+O.</li>
<li><b>Keyboard:</b> Run )" PROGRAM_EXE R"( and press )" COMMAND_KEY_PLUS R"(O.</li>
<li><b>File Explorer:</b> Drag a .tilemap file onto )" PROGRAM_EXE R"(.</li>
<li><b>GUI:</b> Drag a .tilemap file onto the tilemap canvas (right) of an open )" PROGRAM_NAME R"( window. You can also drag a tileset image file onto the tileset array (left) to load its graphics.</li>
<li><b>Command Prompt:</b> Pass the .tilemap filename (and optionally a tileset filename too) as an argument to )" PROGRAM_EXE R"(:<br><font size="2"><kbd>)" PROGRAM_EXE " gfx" DIR_SEP "pokegear" DIR_SEP "johto.bin gfx" DIR_SEP "pokegear" DIR_SEP R"(town_map.png</kbd></font><br>You can also pass the name of an importable file (.c, .asm/.inc, .csv, or .rmp) to import it, or an image file (.png, .bmp, or .gif) to start Image to Tiles with it.</li>
Expand Down Expand Up @@ -50,15 +50,15 @@ <h1>)" PROGRAM_NAME R"(</h1>
<li>Right-click a tile in the tilemap canvas to select it in the tileset.</li>
<li>Right-click and drag in the tilemap canvas to select a rectangle of tiles.</li>
<li>Middle-click and drag to scroll the tileset or tilemap.</li>
<li>Hold Shift and left-click a group of tiles to flood-fill it with the selected type.</li>
<li>Hold Ctrl and left-click a tile to replace every tile of that type with the selected type.</li>
<li>Hold Alt and left-click a tile to swap every tile of that type and every tile of the selected type.</li>
<li>Hold )" SHIFT_KEY R"( and left-click a group of tiles to flood-fill it with the selected type.</li>
<li>Hold )" CONTROL_KEY R"( and left-click a tile to replace every tile of that type with the selected type.</li>
<li>Hold )" ALT_KEY R"( and left-click a tile to swap every tile of that type and every tile of the selected type.</li>
</ul>
<p>The arrow keys, or the mouse's scrolling function if it has one, will scroll the tileset or tilemap (whichever one the cursor is over). This can be done while dragging to select a rectangle of tiles, in order to select a rectangle larger than the visible area.</p>
<hr>
<p>Usually a tilemap only uses one tileset image, which starts from tile $0:00. For these you can just use the Load Tileset function (Ctrl+T or the toolbar's tileset button with a blue arrow). For example, pokered's gfx)" DIR_SEP "town_map.rle uses gfx" DIR_SEP R"(town_map.png.</p>
<p>Usually a tilemap only uses one tileset image, which starts from tile $0:00. For these you can just use the Load Tileset function ()" COMMAND_KEY_PLUS R"(T or the toolbar's tileset button with a blue arrow). For example, pokered's gfx)" DIR_SEP "town_map.rle uses gfx" DIR_SEP R"(town_map.png.</p>
<p>Sometimes a .png tileset has redundant tiles that get eliminated when you <kbd>make</kbd> the ROM. In those cases, just load the built .1bpp, .2bpp, .4bpp, or .8bpp tileset instead. Compressed .lz files (the Pokémon GSC kind, not the GBA kind) are also supported; so are NDS .rgcn/.ncgr files.</p>
<p>Some tilemaps may also use more than one tileset. For example, pokecrystal's gfx)" DIR_SEP "pokegear" DIR_SEP "radio.tilemap.rle uses tiles from gfx" DIR_SEP "pokegear" DIR_SEP "town_map.png, gfx" DIR_SEP "pokegear" DIR_SEP "pokegear.png, and gfx" DIR_SEP "font" DIR_SEP R"(font_extra.png. For these you can use the Add Tileset function (Ctrl+A or the toolbar's tileset button with a green plus sign). This lets you load another tileset in addition to any you've already loaded, and can configure how it gets loaded:</p>
<p>Some tilemaps may also use more than one tileset. For example, pokecrystal's gfx)" DIR_SEP "pokegear" DIR_SEP "radio.tilemap.rle uses tiles from gfx" DIR_SEP "pokegear" DIR_SEP "town_map.png, gfx" DIR_SEP "pokegear" DIR_SEP "pokegear.png, and gfx" DIR_SEP "font" DIR_SEP R"(font_extra.png. For these you can use the Add Tileset function ()" COMMAND_KEY_PLUS R"(A or the toolbar's tileset button with a green plus sign). This lets you load another tileset in addition to any you've already loaded, and can configure how it gets loaded:</p>
<ul>
<li><b>Start at ID:</b> Which tile ID to begin at, instead of $0:00.</li>
<li><b>Offset:</b> Skip this many tiles from the beginning of the image.</li>
Expand All @@ -74,7 +74,7 @@ <h1>)" PROGRAM_NAME R"(</h1>
<p>The general-purpose GBC, GBA, SGB, SNES, Genesis, and TG16 formats all support palettes. Each tile in the tilemap has a corresponding palette ID. When you choose the Palettes tab instead of the Tiles tab, these can be viewed and edited similarly to the tiles.</p>
<p>The palette colors are arbitrary; there is no support for using or editing the actual colors displayed in-game. For some projects, the tileset image will already have the right colors; for others, it will be monochrome. You may want to make a colored-in copy of your tileset to help design tilemaps, like the example)" DIR_SEP "pokecrystal" DIR_SEP R"(town_map_pokegear.png image.</p>
<hr>
<p>)" PROGRAM_NAME R"( is mainly for editing tilemaps using tilesets that already exist, but it can also create a tilemap and tileset, and optionally a palette, from a screenshot with the Image to Tiles function (Ctrl+X or the toolbar's brown picture button). For example, if you want to display a custom full-screen picture, you might draw a 160x144-pixel (20x18-tile) mockup. You can then create a tilemap and tileset from that mockup, as long as it doesn't need too many unique tiles. Duplicate tiles will not be included in the tileset; this takes X/Y flipped tiles into account if the chosen format supports it.</p>
<p>)" PROGRAM_NAME R"( is mainly for editing tilemaps using tilesets that already exist, but it can also create a tilemap and tileset, and optionally a palette, from a screenshot with the Image to Tiles function ()" COMMAND_KEY_PLUS R"(X or the toolbar's brown picture button). For example, if you want to display a custom full-screen picture, you might draw a 160x144-pixel (20x18-tile) mockup. You can then create a tilemap and tileset from that mockup, as long as it doesn't need too many unique tiles. Duplicate tiles will not be included in the tileset; this takes X/Y flipped tiles into account if the chosen format supports it.</p>
<p>The tileset image uses the current tileset width (which is 16 tiles by default). If the number of tiles in the tileset is not a multiple of 16, there will be extra blank tiles at the end of the image. Checking the option to avoid this will pick a different image size with a width that evenly divides the number of tiles, so there will be no extra tiles. (If the number of tiles is prime, this can output a tall tileset image that's one tile wide.)</p>
<p>If you enable creating a palette, you must also select a format for it. The indexed color format will embed the palette directly in the tileset image (as a PLTE chunk for PNG images, or a color table for BMP images). The assembly (RGB) format is for the .asm macros used by Gen 1 and 2 Pokémon disassemblies. The others are standard palette file formats from various graphics programs. The tileset will be grayscale if its palette is output to a separate file. Palettes are rounded from the input 8-bit RGB channels to the GBC/GBA 5-bit channels, and sorted from lightest to darkest color.</p>
<p>Creating a palette also lets you specify a color #0. Every palette will use this same color for its 0th slot, even if the color does not appear in the input image. This is useful for graphics that need a "transparent" background color, e.g. sprites. The color is specified by entering an #RRGGBB color code (or on Windows, by clicking the color preview swatch to open the standard color picker). It gets rounded down from 8-bit to 5-bit channels, like all other colors.</p>
Expand Down
12 changes: 12 additions & 0 deletions src/cocoa.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#ifndef COCOA_H
#define COCOA_H

#pragma warning(push, 0)
#include <FL/Fl_Window.H>
#pragma warning(pop)

void cocoa_set_window_transparency(const Fl_Window *w, double alpha);
void cocoa_set_appearance(const Fl_Window *w, bool dark);
bool cocoa_is_dark_mode();

#endif
27 changes: 27 additions & 0 deletions src/cocoa.mm
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
#import <Cocoa/Cocoa.h>

#pragma warning(push, 0)
#include <FL/x.H>
#pragma warning(pop)

#include "cocoa.h"

void cocoa_set_window_transparency(const Fl_Window *w, double alpha) {
[fl_xid(w) setAlphaValue:alpha];
}

void cocoa_set_appearance(const Fl_Window *w, bool dark) {
NSAppearance *appearance = [NSAppearance appearanceNamed:NSAppearanceNameAqua];
#ifdef MAC_OS_X_VERSION_10_14
if (@available(macOS 10.14, *)) {
if (dark) {
appearance = [NSAppearance appearanceNamed:NSAppearanceNameDarkAqua];
}
}
#endif
[fl_xid(w) setAppearance: appearance];
}

bool cocoa_is_dark_mode() {
return [[[NSUserDefaults standardUserDefaults] stringForKey:@"AppleInterfaceStyle"] isEqualToString:@"Dark"];
}
Loading