Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
af1573d
Themes working
Dec 10, 2025
b6b0b6d
Anchor and styling added
Dec 10, 2025
d25d306
List done - capsule needs tweaking
Dec 11, 2025
de65cca
Defaults working - ish
Dec 12, 2025
da4a3e8
Subtle tweaks
Dec 15, 2025
5ae648b
Theme variables utalized
Dec 15, 2025
72671c8
Themes working
Dec 15, 2025
b1b4e66
Added carousel theme
Dec 15, 2025
9b9d597
Added carousel theme
Dec 15, 2025
3d41457
Wokring
Dec 16, 2025
cb1e904
Navigation still not working as it should
Dec 16, 2025
9dd13f6
Navigation now working
Dec 17, 2025
51110da
Navigation working, and now with custom overwrites if needed
Dec 17, 2025
1ba9576
Working
Dec 17, 2025
2375c38
Working
Dec 18, 2025
df4ef53
XML Restructure done
Dec 18, 2025
72f5f60
Snowcase added
Dec 18, 2025
e2d413d
Working
Dec 18, 2025
09828d0
Updated zIndex calculations
Dec 19, 2025
9a1a68b
Good
Dec 19, 2025
660cc53
Fixed minor issues
Dec 22, 2025
df4b2a2
Last played support
Dec 22, 2025
275b48d
Working on SwitchinItUp
Dec 22, 2025
8de5caa
Almost done with SwitchinItUp
Dec 22, 2025
55b25d5
Working, but need small adjustments
Dec 22, 2025
42642ed
Performance optimized carousels, and fixed some bugs
Dec 30, 2025
4e51027
Added Add External Theme functionality
Dec 31, 2025
9c8f3cb
Added documentation
Jan 1, 2026
5b2393e
Updated documentation
Jan 2, 2026
428f486
Merged master into themes
Jan 5, 2026
69d797a
Added translations
Jan 5, 2026
2b7ce94
Moved code styling inline
Jan 5, 2026
d9bccd5
Fixed misspelling
Jan 5, 2026
505cc2e
Merged with Master, and added GOG support for themes
Jan 6, 2026
fe4610a
Pruning for PR
Jan 7, 2026
8cc1f86
Pruned for PR
Jan 7, 2026
271da96
Merge master into themes: integrate upstream features with theme engine
Mar 2, 2026
80bf781
Fix cross-platform theme test failures
Mar 2, 2026
fd06204
Fix black screen on game detail page in themed UI
Mar 2, 2026
93330e0
Fix focus not restoring after dialog dismissal in themed UI
Mar 2, 2026
dbd0a5d
Auto-restore grid focus when dialog is dismissed
Mar 2, 2026
4ec82e5
Fix focus restoration after dialog dismissal and touch events
Mar 2, 2026
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
7 changes: 7 additions & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -241,8 +241,15 @@ dependencies {
implementation(platform(libs.androidx.compose.bom))
implementation(libs.bundles.compose)
implementation(libs.landscapist.coil)
implementation(libs.coil.svg)
debugImplementation(libs.androidx.ui.tooling)

// Media3 (ExoPlayer) for video playback
implementation(libs.bundles.media3)

// Baseline Profiles - enables precompilation of critical code paths
implementation(libs.profileinstaller)

// Support
implementation(libs.androidx.core.ktx)
implementation(libs.androidx.lifecycle.runtime.ktx)
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
13 changes: 13 additions & 0 deletions app/src/main/assets/Themes/CapsuleGrid/manifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<manifest>
<id>capsule_grid</id>
<title>Capsule Grid</title>
<version>1.0.0</version>
<engineVersion>1.0.0</engineVersion>
<minAppVersion>0.0.0</minAppVersion>
<description>A responsive grid of portrait capsule art with compatibility badges and game titles.</description>
<author>
<name>Radical Thomas</name>
</author>
<preview src="/assets/theme.png" />
<theme source="/theme.xml" variables="/variables.xml" />
</manifest>
64 changes: 64 additions & 0 deletions app/src/main/assets/Themes/CapsuleGrid/theme.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
<theme>
<elements>
<card id="capsule" width="100%" height="100%">
<image id="art" x="0" y="0" width="100%" height="100%"
src="@{game.capsule}" cornerRadius="@{vars.cornerRadius}" scaleType="cover" />

<rect id="compatBadge" x="0" y="0" width="100%" height="@{vars.badgeHeight}"
color="@{vars.overlayColor}" cornerRadius="@{vars.cornerRadius} @{vars.cornerRadius} 0 0" />
<text id="compatLabel" x="0" y="0" width="100%" height="@{vars.badgeHeight}"
text="@{game.compatibility.label}" textSize="@{vars.textSizeSmall}"
color="@{game.compatibility.color}" maxLines="1" textAlign="center" fontWeight="bold" />

<rect id="footer" x="0" y="0" width="100%" height="@{vars.badgeHeight}"
color="@{vars.overlayColor}" cornerRadius="0 0 @{vars.cornerRadius} @{vars.cornerRadius}"
anchor="bottomLeft" />
<text id="title" x="@{vars.paddingSmall}" y="2" width="80%" height="@{vars.badgeHeight}"
text="@{game.title}" textSize="@{vars.textSizeSmall}" color="@{vars.textColor}"
maxLines="1" textAlign="left" fontWeight="bold" anchor="bottomLeft" />

<text id="installedCheck" x="16" y="2" height="@{vars.badgeHeight}"
text="✓" textSize="@{vars.textSizeSmall}" color="@{vars.textColor}"
textAlign="right" fontWeight="bold" anchor="bottomRight"
visibleWhen="@{game.isInstalled}" />

<rect id="backdrop" x="0" y="0" width="100%" height="100%"
color="#00000000" borderWidth="1" borderColor="#22FFFFFF"
cornerRadius="@{vars.cornerRadius}" />
</card>
</elements>

<layout>
<grid cellWidth="150" aspectRatio="0.67"
hSpacing="@{vars.gridSpacing}" vSpacing="@{vars.gridSpacing}"
padding="@{vars.gridPaddingTop} 20"
highlightCornerRadius="@{vars.cornerRadius}"
verticalAlign="center"
selectionMode="moving" itemCard="capsule" />

<fixed id="topBar" backgroundColor="#CC000000" height="@{vars.topBarHeight}">
<header x="@{vars.paddingMedium}" y="@{vars.paddingSmall}" anchor="topLeft"
textColor="@{vars.textColor}" showAppName="true" showGameCount="true" />
<searchBar x="@{vars.searchBarX}" y="@{vars.searchBarY}" width="@{vars.searchBarWidth}" height="56"
anchor="topLeft" backgroundColor="#FF1E1E1E" borderRadius="24"
highlightColor="@{vars.highlightColor}" highlightOpacity="@{vars.highlightOpacity}"
highlightBorderWidth="@{vars.highlightBorderWidth}" highlightTransitionSpeed="@{vars.highlightTransitionSpeed}" />
<profileButton x="@{vars.paddingMedium}" y="@{vars.profileButtonY}" anchor="topRight" size="56"
iconSize="36" padding="10"
backgroundColor="#66808080" cornerRadius="12"
highlightColor="@{vars.highlightColor}" highlightOpacity="@{vars.highlightOpacity}"
highlightBorderWidth="@{vars.highlightBorderWidth}" highlightTransitionSpeed="@{vars.highlightTransitionSpeed}" />
</fixed>

<fixed id="bottomBar">
<filterButton x="100" y="@{vars.bottomOffset}" anchor="bottomRight" expanded="true"
cornerRadius="16" size="56" iconSize="24"
highlightColor="@{vars.highlightColor}" highlightOpacity="@{vars.highlightOpacity}"
highlightBorderWidth="@{vars.highlightBorderWidth}" highlightTransitionSpeed="@{vars.highlightTransitionSpeed}" />
<addButton x="@{vars.paddingMedium}" y="@{vars.bottomOffset}" anchor="bottomRight"
cornerRadius="16" size="56" iconSize="24"
highlightColor="@{vars.highlightColor}" highlightOpacity="@{vars.highlightOpacity}"
highlightBorderWidth="@{vars.highlightBorderWidth}" highlightTransitionSpeed="@{vars.highlightTransitionSpeed}" />
</fixed>
</layout>
</theme>
40 changes: 40 additions & 0 deletions app/src/main/assets/Themes/CapsuleGrid/variables.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<variables>
<!-- Colors (used multiple times) -->
<var name="textColor" value="#FFFFFFFF" />
<var name="overlayColor" value="#99000000" />
<var name="highlightColor" value="#9575CD" />

<!-- Highlight styling (used by multiple elements) -->
<var name="highlightOpacity" value="0.9" />
<var name="highlightBorderWidth" value="2" />
<var name="highlightTransitionSpeed" value="200" />

<!-- Shared dimensions -->
<var name="cornerRadius" value="20" />
<var name="badgeHeight" value="20" />
<var name="gridSpacing" value="12" />
<var name="textSizeSmall" value="11" />

<!-- Shared spacing -->
<var name="paddingSmall" value="8" />
<var name="paddingMedium" value="16" />
<var name="bottomOffset" value="24" />

<!-- Top bar (in breakpoint) -->
<var name="topBarHeight" value="72" />
<var name="searchBarWidth" value="600" />
<var name="searchBarX" value="200" />
<var name="searchBarY" value="12" />
<var name="profileButtonY" value="12" />
<var name="gridPaddingTop" value="80" />

<!-- Portrait overrides -->
<breakpoint orientation="portrait">
<var name="topBarHeight" value="130" />
<var name="gridPaddingTop" value="160" />
<var name="searchBarWidth" value="90%" />
<var name="searchBarX" value="5%" />
<var name="searchBarY" value="80" />
<var name="profileButtonY" value="10" />
</breakpoint>
</variables>
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
14 changes: 14 additions & 0 deletions app/src/main/assets/Themes/Carousel/manifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<manifest>
<id>carousel</id>
<title>Carousel</title>
<version>1.0.0</version>
<engineVersion>1.0.0</engineVersion>
<minAppVersion>0.0.0</minAppVersion>
<description>A center-focused carousel with large hero images. The highlighted game in the center scales up for emphasis.</description>
<author>
<name>Mikkel Bjørnmose Bundgaard</name>
<github>https://github.com/maaggel</github>
</author>
<preview src="/assets/theme.png" />
<theme source="/theme.xml" variables="/variables.xml" />
</manifest>
71 changes: 71 additions & 0 deletions app/src/main/assets/Themes/Carousel/theme.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
<theme>
<elements>
<card id="carousel" width="@{vars.cardWidth}" height="@{vars.cardHeight}">
<image id="hero" x="0" y="0" width="100%" height="@{vars.heroHeight}"
src="@{game.hero}" cornerRadius="@{vars.cornerRadius}" scaleType="cover" />

<rect id="border" x="0" y="0" width="100%" height="@{vars.heroHeight}"
color="#00000000" borderWidth="2" borderColor="#33FFFFFF"
cornerRadius="@{vars.cornerRadius}" />

<text id="title" x="0" y="@{vars.infoY}" width="100%"
text="@{game.title}" textSize="@{vars.textSizeTitle}" color="@{vars.infoTextColor}"
maxLines="1" textAlign="center" fontWeight="medium" anchor="topLeft"
focusOnly="true" focusTransitionSpeed="@{vars.transitionSpeed}" />

<text id="compatLabel" x="0" y="@{vars.compatY}" width="100%"
text="@{game.compatibility.label}" textSize="@{vars.textSizeSmall}"
color="@{game.compatibility.color}" maxLines="1" textAlign="center" fontWeight="normal"
anchor="topLeft" focusOnly="true" focusTransitionSpeed="@{vars.transitionSpeed}" opacity="0.7" />

<text id="installedCheck" x="10" y="170"
text="✓" textSize="@{vars.textSizeSmall}" color="@{vars.infoTextColor}"
textAlign="right" fontWeight="bold" anchor="topRight"
focusOnly="true" focusTransitionSpeed="@{vars.transitionSpeed}"
visibleWhen="@{game.isInstalled}" />
Comment thread
coderabbitai[bot] marked this conversation as resolved.
</card>
</elements>

<layout>
<carousel
centerFocus="true"
focusedScale="@{vars.focusedScale}"
verticalAlign="center"
verticalOffset="@{vars.carouselOffsetY}"
itemWidth="@{vars.cardWidth}"
itemHeight="@{vars.cardHeight}"
itemSpacing="@{vars.itemSpacing}"
selectionMode="stationary"
itemCard="carousel"
focusedBackground="@{game.libraryHero}"
backgroundOpacity="0.2"
backgroundTransitionSpeed="@{vars.transitionSpeed}"
highlightBorderWidth="0" />

<fixed id="topBar" height="@{vars.topBarHeight}">
<header x="@{vars.paddingMedium}" y="8" anchor="topLeft"
textColor="#FFFFFFFF" showAppName="true" showGameCount="true" />
<searchBar x="76" y="12" width="@{vars.searchBarWidth}" height="46"
anchor="topRight" backgroundColor="#CC1E1E1E" borderRadius="28"
collapsible="true"
highlightColor="@{vars.highlightColor}" highlightOpacity="@{vars.highlightOpacity}"
highlightBorderWidth="@{vars.highlightBorderWidth}" highlightTransitionSpeed="@{vars.highlightTransitionSpeed}" />
<profileButton x="12" y="@{vars.profileButtonY}" anchor="topRight" size="48"
iconSize="48" padding="2"
backgroundColor="#CC1E1E1E" cornerRadius="27"
highlightColor="@{vars.highlightColor}" highlightOpacity="@{vars.highlightOpacity}"
highlightBorderWidth="@{vars.highlightBorderWidth}" highlightTransitionSpeed="@{vars.highlightTransitionSpeed}" />
</fixed>

<fixed id="bottomBar">
<filterButton x="@{vars.paddingMedium}" y="@{vars.bottomOffset}" anchor="bottomLeft" expanded="true"
cornerRadius="16" size="56" iconSize="24"
highlightColor="@{vars.highlightColor}" highlightOpacity="@{vars.highlightOpacity}"
highlightBorderWidth="@{vars.highlightBorderWidth}" highlightTransitionSpeed="@{vars.highlightTransitionSpeed}" />
<addButton x="@{vars.paddingMedium}" y="@{vars.bottomOffset}" anchor="bottomRight"
cornerRadius="16" size="56" iconSize="24"
highlightColor="@{vars.highlightColor}" highlightOpacity="@{vars.highlightOpacity}"
highlightBorderWidth="@{vars.highlightBorderWidth}" highlightTransitionSpeed="@{vars.highlightTransitionSpeed}" />
</fixed>
</layout>
</theme>
60 changes: 60 additions & 0 deletions app/src/main/assets/Themes/Carousel/variables.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
<variables>
<!-- Colors (used multiple times) -->
<var name="infoTextColor" value="#AAFFFFFF" />
<var name="highlightColor" value="#9575CD" />

<!-- Highlight styling (used by multiple elements) -->
<var name="highlightOpacity" value="0.9" />
<var name="highlightBorderWidth" value="2" />
<var name="highlightTransitionSpeed" value="200" />

<!-- Shared spacing -->
<var name="paddingMedium" value="16" />
<var name="bottomOffset" value="24" />
<var name="transitionSpeed" value="400" />

<!-- Card options -->
<var name="cardWidth" value="360" />
<var name="cardHeight" value="228" />
<var name="heroHeight" value="168" />
<var name="cornerRadius" value="8" />
<var name="itemSpacing" value="24" />
<var name="badgeHeight" value="20" />

<!-- Info positioning -->
<var name="infoY" value="170" />
<var name="compatY" value="182" />

<!-- Carousel settings -->
<var name="focusedScale" value="1.4" />
<var name="carouselOffsetY" value="42" />

<!-- Text -->
<var name="textSizeTitle" value="12" />
<var name="textSizeSmall" value="10" />

<!-- Top bar -->
<var name="topBarHeight" value="72" />
<var name="searchBarWidth" value="300" />
<var name="profileButtonY" value="12" />

<!-- Portrait overrides -->
<breakpoint orientation="portrait">
<var name="topBarHeight" value="130" />
<var name="searchBarWidth" value="90%" />
<var name="profileButtonY" value="0" />

<var name="cardWidth" value="220" />
<var name="cardHeight" value="142" />
<var name="heroHeight" value="103" />
<var name="itemSpacing" value="12" />
<var name="carouselOffsetY" value="30" />
<var name="focusedScale" value="1.3" />

<var name="infoY" value="108" />
<var name="compatY" value="125" />

<var name="textSizeTitle" value="11" />
<var name="textSizeSmall" value="8" />
</breakpoint>
</variables>
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
13 changes: 13 additions & 0 deletions app/src/main/assets/Themes/HeroGrid/manifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<manifest>
<id>hero_grid</id>
<title>Hero Grid</title>
<version>1.0.0</version>
<engineVersion>1.0.0</engineVersion>
<minAppVersion>0.0.0</minAppVersion>
<description>A responsive grid of landscape hero art with compatibility badges and game titles. Adapts to portrait and landscape orientations.</description>
<author>
<name>Radical Thomas</name>
</author>
<preview src="/assets/theme.png" />
<theme source="/theme.xml" variables="/variables.xml" />
</manifest>
63 changes: 63 additions & 0 deletions app/src/main/assets/Themes/HeroGrid/theme.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
<theme>
<elements>
<card id="hero" width="100%" height="100%">
<image id="art" x="0" y="0" width="100%" height="100%"
src="@{game.hero}" cornerRadius="@{vars.cornerRadius}" scaleType="cover" />

<rect id="compatBadge" x="0" y="0" width="100%" height="@{vars.badgeHeight}"
color="@{vars.overlayColor}" cornerRadius="@{vars.cornerRadius} @{vars.cornerRadius} 0 0" />
<text id="compatLabel" x="0" y="-2" width="100%" height="@{vars.badgeHeight}"
text="@{game.compatibility.label}" textSize="@{vars.textSizeSmall}"
color="@{game.compatibility.color}" maxLines="1" textAlign="center" fontWeight="bold" />

<rect id="footer" x="0" y="0" width="100%" height="@{vars.badgeHeight}"
color="@{vars.overlayColor}" cornerRadius="0 0 @{vars.cornerRadius} @{vars.cornerRadius}"
anchor="bottomLeft" />
<text id="title" x="@{vars.paddingSmall}" y="2" width="80%" height="@{vars.badgeHeight}"
text="@{game.title}" textSize="@{vars.textSizeSmall}" color="@{vars.textColor}"
maxLines="1" textAlign="left" fontWeight="bold" anchor="bottomLeft" />

<text id="installedCheck" x="16" y="2" height="@{vars.badgeHeight}"
text="✓" textSize="@{vars.textSizeSmall}" color="@{vars.textColor}"
textAlign="right" fontWeight="bold" anchor="bottomRight"
visibleWhen="@{game.isInstalled}" />

<rect id="backdrop" x="0" y="0" width="100%" height="100%"
color="#00000000" borderWidth="1" borderColor="#22FFFFFF"
cornerRadius="@{vars.cornerRadius}" />
</card>
</elements>

<layout>
<grid cellWidth="200" aspectRatio="2.14"
hSpacing="@{vars.hSpacing}" vSpacing="15"
padding="@{vars.gridPaddingTop} 20"
verticalAlign="center"
selectionMode="moving" itemCard="hero" />

<fixed id="topBar" backgroundColor="#CC000000" height="@{vars.topBarHeight}">
<header x="@{vars.paddingMedium}" y="@{vars.paddingSmall}" anchor="topLeft"
textColor="@{vars.textColor}" showAppName="true" showGameCount="true" />
<searchBar x="@{vars.searchBarX}" y="@{vars.searchBarY}" width="@{vars.searchBarWidth}" height="56"
anchor="topLeft" backgroundColor="#FF1E1E1E" borderRadius="24"
highlightColor="@{vars.highlightColor}" highlightOpacity="@{vars.highlightOpacity}"
highlightBorderWidth="@{vars.highlightBorderWidth}" highlightTransitionSpeed="@{vars.highlightTransitionSpeed}" />
<profileButton x="@{vars.paddingMedium}" y="@{vars.profileButtonY}" anchor="topRight" size="56"
iconSize="36" padding="10"
backgroundColor="#66808080" cornerRadius="12"
highlightColor="@{vars.highlightColor}" highlightOpacity="@{vars.highlightOpacity}"
highlightBorderWidth="@{vars.highlightBorderWidth}" highlightTransitionSpeed="@{vars.highlightTransitionSpeed}" />
</fixed>

<fixed id="bottomBar">
<filterButton x="100" y="@{vars.bottomOffset}" anchor="bottomRight" expanded="true"
cornerRadius="16" size="56" iconSize="24"
highlightColor="@{vars.highlightColor}" highlightOpacity="@{vars.highlightOpacity}"
highlightBorderWidth="@{vars.highlightBorderWidth}" highlightTransitionSpeed="@{vars.highlightTransitionSpeed}" />
<addButton x="@{vars.paddingMedium}" y="@{vars.bottomOffset}" anchor="bottomRight"
cornerRadius="16" size="56" iconSize="24"
highlightColor="@{vars.highlightColor}" highlightOpacity="@{vars.highlightOpacity}"
highlightBorderWidth="@{vars.highlightBorderWidth}" highlightTransitionSpeed="@{vars.highlightTransitionSpeed}" />
</fixed>
</layout>
</theme>
Loading