Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

new theme file structure #30

Merged
merged 10 commits into from
Feb 25, 2024
58 changes: 21 additions & 37 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,43 +122,28 @@ Out of the box, there are 2 ways you can add custom themes:
- Files with the file ending `.theme.json` stored in a `themes` directory of the working dir.
- Assembly resources in any assembly where the name starts with `CONFIG_THEMING_THEME_`

Both ways use the same JSON format for the theme definition(the version defines the format of the file):
Both ways use the same JSON format for the theme definition(the version defines the format of the file).
A simple example of this could be:
```json
{
"name": "theme-name",
"capabilities": ["DarkMode", "LightMode", "HighContrast"],
"version": 2,
"colors": {
"backColor": "#082a56",
"foreColor": "#082a56",
"buttonBackColor": "#082a56",
"buttonForeColor": "#082a56",
"buttonHoverColor": "#082a56",
"comboBoxItemBackColor": "#082a56",
"comboBoxItemHoverColor": "#082a56",
"controlBackColor": "#082a56",
"controlForeColor": "#082a56",
"controlHighlightColor": "#082a56",
"controlHighlightLightColor": "#082a56",
"controlHighlightDarkColor": "#082a56",
"controlBorderColor": "#082a56",
"controlBorderLightColor": "#082a56",
"listViewHeaderGroupColor": "#082a56",
"tableBackColor": "#082a56",
"tableHeaderBackColor": "#082a56",
"tableHeaderForeColor": "#082a56",
"tableSelectionBackColor": "#082a56",
"tableCellBackColor": "#082a56",
"tableCellForeColor": "#082a56",
"successBackColor": "#082a56",
"successForeColor": "#082a56",
"warningBackColor": "#082a56",
"warningForeColor": "#082a56",
"errorBackColor": "#082a56",
"errorForeColor": "#082a56"
}
"name": "theme-name",
"capabilities": ["DarkMode", "HighContrast"],
"version": 3,
"variables": {
"backColor": "#082a56",
"foreColor": "#082a57"
},
"colors": {
"backColor": "backColor",
"foreColor": "foreColor",
"controls": {
"backColor": "backColor",
"foreColor": "foreColor"
}
}
}
```
For the complete list of available settings please check our JSON schema [here](https://github.com/Assorted-Development/winforms-themes/blob/main/WinFormsThemes/WinFormsThemes/themes.schema.json).

If those 2 ways are not flexible enough, you can implement a theme by yourself and register it using a custom theme source (see below):
The prefered way is to subclass `AbstractTheme` as you just need to implement the base colors and optionally override the extended colors - styling the controls is done by the base class.
Expand Down Expand Up @@ -205,19 +190,18 @@ After this, you need to register this class in the builder:
As we do not want to force you to use a specific WinForms control library, we currently only support styling of standard controls and controls from our [winforms-stylable-controls](https://github.com/Assorted-Development/winforms-stylable-controls) project.
As we understand you may want to also style other controls, we support adding specialised plugins to handle styling of a specific type of control. To do this, you need to implement ``:
```csharp
internal class MyCustomControlThemePlugin : IThemePlugin
internal class MyCustomControlThemePlugin : AbstractThemePlugin<MyCustomControl>
{
public void Apply(Control control, AbstractTheme theme)
protected override void ApplyPlugin(MyCustomControl mcc, AbstractTheme theme)
{
MyCustomControl mcc = (MyCustomControl)control;
//style control based on the colors available in the Theme
}
}
```
At last, you just need to register it for the correct type:
```csharp
ThemeRegistryHolder.GetBuilder()
.AddThemePlugin<MyCustomControl>(new MyCustomControlThemePlugin())
.AddThemePlugin(new MyCustomControlThemePlugin())
.Build();
```

Expand Down
26 changes: 26 additions & 0 deletions SECURITY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Security Policy

## Supported Versions

| Version | Supported | Comment |
| ------- | ------------------ | ------- |
| 1.x | :white_check_mark: | not yet released |
| <1.x | :x: | those are prereleases and should not be used in production |

## Reporting a Vulnerability
**Please do not report security vulnerabilities through public GitHub issues.**

If you find a vulnerability, please report it to security@nockiro.de. We will get in contact with you about the details and inform you as soon as we have any updates.

Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue:

* Type of issue (e.g. missing input validation, native code execution, ...)
* The location of the affected source code (tag/branch/commit or direct URL)
* Any special configuration required to reproduce the issue
* Step-by-step instructions to reproduce the issue
* Proof-of-concept or exploit code (if possible)
* Impact of the issue, including how an attacker might exploit the issue

## Preferred Languages

We support communications in English or German.
12 changes: 6 additions & 6 deletions WinFormsThemes/TestProject/AbstractThemeTest.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System.Windows.Forms;
using System.Windows.Forms;
using WinFormsThemes;
using WinFormsThemes.Themes;

Expand All @@ -13,7 +13,7 @@ public void PluginShouldBeCalledForExactType()
ThemePlugin plugin = new();
IThemeRegistry registry = ThemeRegistryHolder.GetBuilder()
.SetLoggerFactory(LoggerFactory)
.AddThemePlugin<Button>(plugin)
.AddThemePlugin(plugin)
.Build();
using Button button = new();
registry.GetTheme()?.Apply(button);
Expand All @@ -26,7 +26,7 @@ public void PluginShouldNotBeCalledForDifferentType()
ThemePlugin plugin = new();
IThemeRegistry registry = ThemeRegistryHolder.GetBuilder()
.SetLoggerFactory(LoggerFactory)
.AddThemePlugin<Button>(plugin)
.AddThemePlugin(plugin)
.Build();
using Form form = new();
registry.GetTheme()?.Apply(form);
Expand All @@ -38,7 +38,7 @@ public void PluginShouldNotBeCalledForSubType()
{
ThemePlugin plugin = new();
IThemeRegistry registry = ThemeRegistryHolder.GetBuilder().SetLoggerFactory(LoggerFactory)
.AddThemePlugin<Button>(plugin)
.AddThemePlugin(plugin)
.Build();
using MyCustomButton button = new();
registry.GetTheme()?.Apply(button);
Expand All @@ -48,11 +48,11 @@ public void PluginShouldNotBeCalledForSubType()
private class MyCustomButton : Button
{ }

private class ThemePlugin : IThemePlugin
private class ThemePlugin : AbstractThemePlugin<Button>
{
public bool WasCalled { get; private set; }

public void Apply(Control control, AbstractTheme theme)
protected override void ApplyPlugin(Button button, AbstractTheme theme)
{
WasCalled = true;
}
Expand Down
156 changes: 149 additions & 7 deletions WinFormsThemes/TestProject/FileThemeTest.cs
Original file line number Diff line number Diff line change
@@ -1,39 +1,181 @@
using System.Drawing;
using System.Drawing;
using Microsoft.Extensions.Logging;
using TestProject.Properties;
using WinFormsThemes.Extensions;
using WinFormsThemes.Themes;

namespace TestProject
{
[TestClass]
public class FileThemeTest
public class FileThemeTest : AbstractTestClass
{
[TestMethod]
public void LoadShouldNotThrow_MissingCapabilities()
{
FileTheme? theme = FileTheme.Load(Resources.MISSING_CAPS);
FileTheme? theme = FileTheme.Load(Resources.MISSING_CAPS, getLogger());
Assert.IsNull(theme);
}

[TestMethod]
public void LoadShouldNotThrow_MissingColors()
{
FileTheme? theme = FileTheme.Load(Resources.MISSING_COLORS);
FileTheme? theme = FileTheme.Load(Resources.MISSING_COLORS, getLogger());
Assert.IsNotNull(theme);
Assert.AreEqual(SystemColors.Control, theme.BackgroundColor);
}

[TestMethod]
public void LoadShouldNotThrow_MissingName()
{
FileTheme? theme = FileTheme.Load(Resources.MISSING_NAME);
FileTheme? theme = FileTheme.Load(Resources.MISSING_NAME, getLogger());
Assert.IsNull(theme);
}

[TestMethod]
public void LoadShouldNotThrow_NonJson()
{
FileTheme? theme = FileTheme.Load("abc");
FileTheme? theme = FileTheme.Load("abc", getLogger());
Assert.IsNull(theme);
}

[TestMethod]
public void LoadVersionedSimple()
{
FileTheme? theme = FileTheme.Load(@"
{
'name': 'theme-name',
'capabilities': ['DarkMode', 'HighContrast'],
'version': 3,
'variables': {

},
'colors': {
'backColor': '#082a56',
'foreColor': '#082a56',
'controls': {
'backColor': '#082a56',
'foreColor': '#082a56'
}
}
}".Replace("'", "\"", StringComparison.Ordinal), getLogger());
Assert.IsNotNull(theme);
}

[TestMethod]
public void LoadVersionedWithVariable()
{
FileTheme? theme = FileTheme.Load(@"
{
'name': 'theme-name',
'capabilities': ['DarkMode', 'HighContrast'],
'version': 3,
'variables': {
'backColor': '#082a56',
'foreColor': '#082a57'
},
'colors': {
'backColor': 'backColor',
'foreColor': 'foreColor',
'controls': {
'backColor': 'backColor',
'foreColor': 'foreColor'
}
}
}".Replace("'", "\"", StringComparison.CurrentCulture), getLogger());
Assert.IsNotNull(theme);
Assert.AreEqual("#082a56".ToColor(), theme.ControlBackColor);
Assert.AreEqual("#082a57".ToColor(), theme.ControlForeColor);
}

[TestMethod]
public void LoadVersionedInvalidSchema()
{
FileTheme? theme = FileTheme.Load(@"
{
'name': 'theme-name',
'capabilities': ['DarkMode', 'HighContrast'],
'version': 3,
'variables': {

}
}".Replace("'", "\"", StringComparison.CurrentCulture), getLogger());
Assert.IsNull(theme);
}

[TestMethod]
public void LoadVersionedDefaultValue()
{
FileTheme? theme = FileTheme.Load(@"
{
'name': 'theme-name',
'capabilities': ['DarkMode', 'HighContrast'],
'version': 3,
'variables': {

},
'colors': {
'backColor': '#082a56',
'foreColor': '#082a56',
'controls': {
'backColor': '#082a56',
'foreColor': '#082a56'
}
}
}".Replace("'", "\"", StringComparison.CurrentCulture), getLogger());
Assert.IsNotNull(theme);
Assert.AreEqual("#082a56".ToColor(), theme.TableBackColor);
}

[TestMethod]
public void LoadVersionedInvalidColorOrVariable()
{
FileTheme? theme = FileTheme.Load(@"
{
'name': 'theme-name',
'capabilities': ['DarkMode', 'HighContrast'],
'version': 3,
'variables': {

},
'colors': {
'backColor': 'unknown',
'foreColor': '#082a56',
'controls': {
'backColor': '#082a56',
'foreColor': '#082a56'
}
}
}".Replace("'", "\"", StringComparison.CurrentCulture), getLogger());
Assert.IsNull(theme);
}

[TestMethod]
public void LoadVersionedSkipEmptyCapabilities()
{
FileTheme? theme = FileTheme.Load(@"
{
'name': 'theme-name',
'capabilities': ['DarkMode', 'HighContrast', ''],
'version': 3,
'variables': {

},
'colors': {
'backColor': '#082a56',
'foreColor': '#082a56',
'controls': {
'backColor': '#082a56',
'foreColor': '#082a56'
}
}
}".Replace("'", "\"", StringComparison.Ordinal), getLogger());
Assert.IsNotNull(theme);
Assert.AreEqual(0, theme.AdvancedCapabilities.Count);
}

private ILogger getLogger()
{
return new Logger<FileThemeTest>(LoggerFactory);
}
}
}
}
10 changes: 5 additions & 5 deletions WinFormsThemes/TestProject/ThemeRegistryBuilderTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ public class ThemeRegistryBuilderTest : AbstractTestClass
public void AddingThemePluginsShouldWork()
{
IThemeRegistry registry = ThemeRegistryHolder.GetBuilder().SetLoggerFactory(LoggerFactory)
.AddThemePlugin<Button>(new ThemePlugin())
.AddThemePlugin(new ThemePlugin())
.Build();
Assert.AreEqual(1, registry.GetTheme()?.ThemePlugins?.Count);
Assert.AreEqual(typeof(ThemePlugin), registry.GetTheme()?.ThemePlugins?[typeof(Button)]?.GetType());
Expand All @@ -21,8 +21,8 @@ public void AddingThemePluginsShouldWork()
public void AddingThemePluginTwiceShouldThrow()
{
IThemeRegistryBuilder registry = ThemeRegistryHolder.GetBuilder().SetLoggerFactory(LoggerFactory)
.AddThemePlugin<Button>(new ThemePlugin());
InvalidOperationException ex = Assert.ThrowsException<InvalidOperationException>(() => registry.AddThemePlugin<Button>(new ThemePlugin()));
.AddThemePlugin(new ThemePlugin());
InvalidOperationException ex = Assert.ThrowsException<InvalidOperationException>(() => registry.AddThemePlugin(new ThemePlugin()));
Assert.AreEqual("ThemePlugin for Button already added", ex.Message);
}

Expand Down Expand Up @@ -67,9 +67,9 @@ public void DefaultsShouldBeAddedWhenNotSet()
Assert.AreEqual(0, registry.GetTheme(DefaultDarkTheme.THEME_NAME)?.AdvancedCapabilities?.Count);
}

private class ThemePlugin : IThemePlugin
private class ThemePlugin : AbstractThemePlugin<Button>
{
public void Apply(Control control, AbstractTheme theme)
protected override void ApplyPlugin(Button button, AbstractTheme theme)
{ }
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,8 @@ public interface IThemeRegistryBuilder
/// <summary>
/// Add a plugin to handle additional controls for Themes that support it
/// </summary>
/// <typeparam name="T">the Control to handle</typeparam>
/// <param name="plugin">the plugin handling the theming</param>
IThemeRegistryBuilder AddThemePlugin<T>(IThemePlugin plugin) where T : Control;
IThemeRegistryBuilder AddThemePlugin(IThemePlugin plugin);

/// <summary>
/// return the final IThemeRegistry
Expand Down
Loading
Loading