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
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
#Custom File Ignore
App.config
LanguageClassGenerator/LangProvider1.cs
# Golden-image parity test review artifacts (generated by tests)
FoliconTest/Resources/ReferenceOverlays/Review/
# User-specific files
*.rsuser
*.suo
Expand Down
9 changes: 8 additions & 1 deletion FoliCon/App.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,17 @@
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:prism="http://prismlibrary.com/" xmlns:hc="https://handyorg.github.io/handycontrol"
xmlns:langs="clr-namespace:FoliCon.Properties.Langs">
xmlns:langs="clr-namespace:FoliCon.Properties.Langs"
xmlns:convertor="clr-namespace:FoliCon.Modules.Convertor">
<Application.Resources>
<ResourceDictionary>
<langs:LangProvider x:Key="FoliConLangs" />

<!--
App-level so every view can compose a localized format string with bound values;
XAML's own StringFormat cannot be bound to a resource. See LocalizedFormatConverter.
-->
<convertor:LocalizedFormatConverter x:Key="LocalizedFormat" />
<ResourceDictionary.MergedDictionaries>
<hc:ThemeResources UsingSystemTheme="True" />
<ResourceDictionary Source="pack://application:,,,/HandyControl;component/Themes/Theme.xaml" />
Expand Down
33 changes: 32 additions & 1 deletion FoliCon/App.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,31 @@ namespace FoliCon;
public partial class App
{
private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
protected override Window CreateShell() => Container.Resolve<MainWindow>();
protected override Window CreateShell()
{
// Ensure GlobalVariables uses the DI-registered singleton (not a separate instance)
GlobalVariables.SetOverlayProvider(Container.Resolve<IOverlayProvider>());

var shell = Container.Resolve<MainWindow>();

// Fire-and-forget overlay update check on app start
_ = CheckOverlayUpdatesAsync();

return shell;
}

private async Task CheckOverlayUpdatesAsync()
{
try
{
var checker = Container.Resolve<OverlayUpdateChecker>();
await checker.CheckForUpdatesAsync();
}
catch (Exception ex)
{
Logger.Warn(ex, "Overlay update check failed during startup");
}
}

public App()
{
Expand All @@ -37,6 +61,13 @@ protected override void RegisterTypes(IContainerRegistry containerRegistry)
containerRegistry.RegisterDialog<Previewer, PreviewerViewModel>("Previewer");
containerRegistry.RegisterDialog<OnboardingWizard, OnboardingWizardViewModel>("OnboardingWizard");
containerRegistry.RegisterDialogWindow<HandyWindow>();

// Overlay plugin system
containerRegistry.RegisterSingleton<IOverlayProvider, OverlayProvider>();
containerRegistry.RegisterSingleton<IOverlayRepositoryService, OverlayRepositoryService>();
containerRegistry.RegisterSingleton<OverlayUpdateChecker>();
containerRegistry.RegisterDialog<OverlayStore, OverlayStoreViewModel>("OverlayStore");
containerRegistry.RegisterDialog<OverlayDesigner, OverlayDesignerViewModel>("OverlayDesigner");
}

private static void App_DispatcherUnhandledException(object sender, DispatcherUnhandledExceptionEventArgs e)
Expand Down
8 changes: 8 additions & 0 deletions FoliCon/FoliCon.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,14 @@ dineshsolanki.github.io/folicon/</Description>
<HotReloadAutoRestart>true</HotReloadAutoRestart>
<SupportedOSPlatformVersion>10.0.26100.0</SupportedOSPlatformVersion>
</PropertyGroup>
<ItemGroup>
<EmbeddedResource Include="Resources\Overlays\legacy\overlay.json" />
<EmbeddedResource Include="Resources\Overlays\alternate\overlay.json" />
<EmbeddedResource Include="Resources\Overlays\liaher\overlay.json" />
<EmbeddedResource Include="Resources\Overlays\faelpessoal\overlay.json" />
<EmbeddedResource Include="Resources\Overlays\faelpessoal-horizontal\overlay.json" />
<EmbeddedResource Include="Resources\Overlays\windows11\overlay.json" />
</ItemGroup>
</Project>


Expand Down
57 changes: 40 additions & 17 deletions FoliCon/Models/Constants/GlobalVariables.cs
Original file line number Diff line number Diff line change
@@ -1,31 +1,54 @@
namespace FoliCon.Models.Constants;
#nullable enable
namespace FoliCon.Models.Constants;

[Localizable(false)]
internal static class GlobalVariables
{
private static IOverlayProvider? _overlayProvider;

public static IconOverlay IconOverlayType()
{
return IconOverlayTypeString switch
{
"Legacy" => IconOverlay.Legacy,
"Alternate" => IconOverlay.Alternate,
"Liaher" => IconOverlay.Liaher,
"Faelpessoal" => IconOverlay.Faelpessoal,
"FaelpessoalHorizontal" => IconOverlay.FaelpessoalHorizontal,
"Windows11" => IconOverlay.Windows11,
_ => IconOverlay.Alternate
};
}
/// <summary>
/// Gets or creates the static overlay provider instance.
/// Prefer using <see cref="SetOverlayProvider"/> to inject the DI singleton.
/// </summary>
public static IOverlayProvider OverlayProvider => _overlayProvider ??= new OverlayProvider();

public const string mediaInfoFile = "info.folicon";
/// <summary>
/// Sets the overlay provider to the DI-registered singleton.
/// Called during app startup to ensure GlobalVariables and DI share the same instance.
/// </summary>
public static void SetOverlayProvider(IOverlayProvider provider) => _overlayProvider = provider;

private static string IconOverlayTypeString
/// <summary>
/// Returns the active overlay string ID from the persisted tracker setting.
/// </summary>
public static string ActiveOverlayId
{
get
{
var data = Services.Tracker.Store.GetData("PosterIconConfigViewModel");
return data.TryGetValue("p.IconOverlay", out var value) ? value.ToString() : IconOverlay.Liaher.ToString();
if (!data.TryGetValue("p.IconOverlay", out var value))
{
return OverlayConstants.defaultOverlayId;
}
var strValue = value?.ToString();
return strValue switch
{
"Legacy" => "legacy",
"Alternate" => "alternate",
"Liaher" => "liaher",
"Faelpessoal" => "faelpessoal",
"FaelpessoalHorizontal" => "faelpessoal-horizontal",
"Windows11" => "windows11",
null => OverlayConstants.defaultOverlayId,
_ => strValue
};
}
}

/// <summary>
/// Returns the active overlay definition.
/// </summary>
public static PosterOverlayDefinition GetActiveOverlay() => OverlayProvider.ResolveActiveOverlayOrDefault(ActiveOverlayId);

public const string mediaInfoFile = "info.folicon";
}
33 changes: 33 additions & 0 deletions FoliCon/Models/Data/OverlayCatalog.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
namespace FoliCon.Models.Data;

/// <summary>
/// Represents the auto-generated catalog.json from the FoliCon-Overlays repository.
/// </summary>
[Localizable(false)]
public class OverlayCatalog
{
public int SchemaVersion { get; set; }
public DateTime GeneratedAt { get; set; }
public List<OverlayCatalogEntry> Overlays { get; set; } = [];
}

/// <summary>
/// A single overlay entry in the catalog.
/// </summary>
[Localizable(false)]
public class OverlayCatalogEntry
{
public string Id { get; set; } = string.Empty;
public string DisplayName { get; set; } = string.Empty;
public string Author { get; set; } = string.Empty;
public string Description { get; set; } = string.Empty;
public string OverlayVersion { get; set; } = string.Empty;
public string[] Tags { get; set; } = [];
public string PreviewUrl { get; set; } = string.Empty;
public string OverlayBaseUrl { get; set; } = string.Empty;
public string OverlayPath { get; set; } = string.Empty;
public long SizeBytes { get; set; }
public string Sha256 { get; set; } = string.Empty;
public DateTime CreatedAt { get; set; }
public DateTime UpdatedAt { get; set; }
}
179 changes: 179 additions & 0 deletions FoliCon/Models/Data/OverlayLayerConfig.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
#nullable enable
namespace FoliCon.Models.Data;

/// <summary>
/// Defines a base or front image layer in an overlay.
/// </summary>
[Localizable(false)]
public class LayerDefinition
{
/// <summary>
/// Relative path to the image file within the overlay folder.
/// </summary>
public string ImagePath { get; set; } = string.Empty;

/// <summary>
/// WPF Thickness string: "left,top,right,bottom". Supports negative values.
/// </summary>
public string Margin { get; set; } = "0,0,0,0";
}

/// <summary>
/// Configuration for the poster image layer within the overlay.
/// </summary>
[Localizable(false)]
public class PosterConfig
{
/// <summary>
/// WPF Thickness string: "left,top,right,bottom".
/// Applied to the Border when clip/mask is used, or directly to the Image otherwise.
/// </summary>
public string Margin { get; set; } = "0,0,0,0";

/// <summary>
/// CornerRadius string: single value or "tl,tr,br,bl".
/// "0" means no clipping.
/// </summary>
public string ClipRadius { get; set; } = "0";

/// <summary>
/// Explicit clip rectangle as "x,y,width,height".
/// When set, overrides the calculated clip from Margin.
/// Matches the original XAML RectangleGeometry Rect values exactly.
/// </summary>
public string? ClipRect { get; set; }

/// <summary>
/// Optional WPF Thickness for the poster Image inside the Border (when clip/mask is active).
/// Some overlays need a small margin on the inner Image (e.g. "0,0,0,-1").
/// When null, the inner Image has no margin and fills the Border content area.
/// </summary>
public string? PosterInnerMargin { get; set; }

/// <summary>
/// Optional relative path to an opacity mask image within the overlay folder.
/// </summary>
public string? OpacityMaskPath { get; set; }
}

/// <summary>
/// Configuration for the rating badge (shield + text).
/// </summary>
[Localizable(false)]
public class RatingConfig
{
/// <summary>
/// WPF Thickness string for the shield image margin.
/// </summary>
public string ShieldMargin { get; set; } = "160,97,6,5";

/// <summary>
/// WPF Thickness string for the rating text margin.
/// </summary>
public string TextMargin { get; set; } = "189,30,21,24";

public double FontSize { get; set; } = 25;
public string FontFamily { get; set; } = "Castellar";

/// <summary>
/// Optional path to a bundled .ttf/.otf font file within the overlay folder.
/// </summary>
public string? FontSource { get; set; }

/// <summary>
/// System font to use if the primary font is not available.
/// </summary>
public string FontFallback { get; set; } = "Segoe UI";

/// <summary>
/// Width of the rating text block.
/// </summary>
public double TextWidth { get; set; } = 55;

/// <summary>
/// Height of the rating text block.
/// </summary>
public double TextHeight { get; set; } = 46;

/// <summary>
/// Horizontal alignment of the rating text.
/// </summary>
public string TextHorizontalAlignment { get; set; } = "Center";

/// <summary>
/// Vertical alignment of the rating text.
/// </summary>
public string TextVerticalAlignment { get; set; } = "Center";
}

/// <summary>
/// Configuration for the title text (optional).
/// </summary>
[Localizable(false)]
public class TitleConfig
{
public bool IsVisible { get; set; }

/// <summary>
/// WPF Thickness string for the title text margin.
/// </summary>
public string Margin { get; set; } = "0,0,0,0";

/// <summary>
/// Rotation angle in degrees (0-360).
/// </summary>
public double RotationAngle { get; set; }

/// <summary>
/// Rotation origin as normalized point "x,y" (0.0–1.0).
/// </summary>
public string RotationOrigin { get; set; } = "0.5,0.5";

public string FontFamily { get; set; } = "Cormorant";

/// <summary>
/// Optional path to a bundled .ttf/.otf font file within the overlay folder.
/// </summary>
public string? FontSource { get; set; }

/// <summary>
/// System font to use if the primary font is not available.
/// </summary>
public string FontFallback { get; set; } = "Segoe UI";

/// <summary>
/// Text foreground color (WPF color name or hex).
/// </summary>
public string Foreground { get; set; } = "White";

/// <summary>
/// Text trimming mode: None, WordEllipsis, CharacterEllipsis.
/// </summary>
public string Trimming { get; set; } = "WordEllipsis";

/// <summary>
/// Text wrapping mode: NoWrap, Wrap, WrapWithOverflow.
/// </summary>
public string Wrapping { get; set; } = "Wrap";

/// <summary>
/// Visual container for the title: Root or RatingGrid.
/// Root preserves the default behavior for community overlays.
/// </summary>
public string Container { get; set; } = "Root";

/// <summary>
/// Grid row used when Container is RatingGrid.
/// </summary>
public int GridRow { get; set; }

/// <summary>
/// Horizontal alignment of the title text.
/// </summary>
public string HorizontalAlignment { get; set; } = "Left";

/// <summary>
/// Vertical alignment of the title text.
/// </summary>
public string VerticalAlignment { get; set; } = "Top";
}
Loading