-
-
Notifications
You must be signed in to change notification settings - Fork 267
Adding a new component
Components in Auto Dark Mode are usually sufficient to implement most features related to theme switching. For all functionality that is related to switching a theme from light to dark, or vice versa, a component is used. If you find yourself limited by that, please go to the "Adding a new module" section after careful consideration. Components get called on every theme switch and do not get registered to a timer, but to the component manager.
Auto Dark Mode runs modules on a Timer, which in turn call a switch theme instruction. This instruction checks all registered components by calling ComponentNeedsUpdate
to see whether a component needs to run code.
If this method returns true, Switch
is called automatically for those components. And this invokes HandleSwitch
, which is implemented by you!
Since the timer is called periodically, it is important that ComponentNeedsUpdate
returns false
when no change in configuration or state occurred since the last update.
A convention we have adhered to currently is to use a variable (or similar) in each component that tracks if it is currently in dark or light mode.
If a user changes a component setting, this variable needs to either be updated, or set to Unknown
Example for a simple component:
- User changed a setting in a component, but the theme is still the same. However, the component needs to run to put itself back into a known state:
ComponentNeedsUpdate
returnstrue
- User switched from light to dark mode, and component is currently still in state light:
ComponentNeedsUpdate
returnstrue
- User switched from light to light (timer or maybe different setting was changed), but no changes are made to the component configuration:
ComponentNeedsUpdate
returnsfalse
- All other similar cases to the one above:
ComponentNeedsUpdate
returnsfalse
Generally, a component consists of two things: A component class, and a settings class.
Each component class inherits from a Base class called BaseComponent<T>
, where T denominates the Class of the Settings object.
They must be placed in AutoDarkModeSvc\SwichComponents\Addons
class MyComponentSwitch : BaseComponent<object>
{
// if you need a constructor, call it like this
public MyComponentSwitch() : base() { /*your code here*/ }
public override bool ThemeHandlerCompatibility => false;
// see below for more detailed description
protected override bool ComponentNeedsUpdate(SwitchEventArgs e)
{
// your code here
}
protected override void HandleSwitch(SwitchEventArgs e)
{
// your code here
}
}
You are expected to handle all exceptions in the component.
The three main methods/properties you need to implement to get a component to work are:
Method or Property | Purpose |
---|---|
ThemeHandlerCompatibility | true or false depending on whether you want your component to be compatible with full windows theme switching. I.e, does it add something to the native theme switching, or does it mirror a feature? For the former, set to true, for the latter, set to no. Example: Color filters and office switch are compatible, but not setting the apps theme |
ComponentNeedsUpdate | This is the place to set the component update logic as mentioned before |
HandleSwitch | The method that is called when a theme switch is invoked automatically or by a user. This is only called if ComponentNeedsUpdate returned true. You may not call this method on your own, unless you have a really solid explanation as to why you need this. |
Method or Property | Purpose |
---|---|
ForceSwitch | Whether the component must update. Note: This property will return back to false once a theme switch was invoked once with it |
Settings | A settings object containing the component base settings, as well as your custom settings object |
SettingsBefore | the same settings object, but one revision older. In case you need to detect changes in configuration in the component to set the state |
The following methods and properties are available to you to override in case you need extra functionality
// the order in which the component is invoked when switching to light or dark mode
public override int PriorityToLight => 30;
public override int PriorityToDark => 30;
// called whenever the component is enabled for the first time (Settings.Enabled changes to true).
protected override void EnableHook()
{
// your code here
}
// called whenever the component is disabled (Settings.Enabled changes to false).
// The disable hook must set all internally tracked settings to a neutral state
// as components classes are never dereferenced.
protected override void DisableHook()
{
// your code here
}
// see below for more detailed description
protected override void UpdateSettingsState(object newSettings)
{
// your code here
}
// call this when you need logic performed after Auto Dark Mode switched the theme instead of before
protected override void Callback(Theme newTheme, SwitchEventArgs e)
{
// your code here
}
// set this to true if your component will cause DWM to refresh itself
public override bool TriggersDwmrefresh => false;
// set this to true if your component requires a DWM refresh
public override bool NeedsDwmRefresh => false;
It is called whenever the AutoDarkMode Config file is updated and BEFORE ComponentNeedsUpdate
and Switch
.
Note: This method will be called regardless of whether the change affects the component or not
If a setting in the configuration changes that affects the component, you would set its state here such that ComponentNeedsUpdate
returns true
next time, until the component is up-to-date again. Calling the base method is not required because it is empty.
In the HandleSwitch and Callback methods, the SwitchEventArgs, which carry information of why the switch occurred, are available. Available information can be:
- The SwitchSource, which specifies where the switch originated from
- The requested theme
- The time when the theme switch was queued
The component has an NLog Logger available to use, reachable via
-
Logger.Info(string message)
orLogger.Info(Exception ex, string message)
for info messages -
Logger.Warn(string message)
orLogger.Warn(Exception ex, string message)
for warning that are non critical -
Logger.Error(string message)
orLogger.Error(Exception ex, string message)
for errors -
Logger.Debug(string message)
orLogger.Debug(Exception ex, string message)
for debugging purposes (not shown in prod by default)
You should also call Logger.Info
at the end of HandleSwitch
, including
- what the theme is now
- what it was before
- what the mode is
Example taken from log file:
2021-10-04 07:35:12 | Info | AppsSwitch.HandleSwitch: update info - previous: Dark, current: Light, mode: Switch
Each component can either specify a settings object, or if it does not need custom settings, pass object
into the generic type.
Example:
- with settings:
internal class MyComponentSwitch : BaseComponent<MyComponentSwitchSettings>
- without settings:
internal class WallpaperSwitch : BaseComponent<object>
All settings objects must be placed in AutoDarkModeLib\ComponentSettings\Addons
(create directory if doesn't exist).
A settings object inherits from either BaseSettings
or BaseSettingsEnabled
, if the component needs to be enabled by default. Please only use the latter if a prior agreement with the maintainers was reached.
Create a new class that represent your settings. Please follow the convention of the other modules.
Usually, for setting the switch modes, we use a Mode enum that contains the following values:
public enum Mode
{
Switch = 0, // meant to be used if the component should run when a theme switch is invoked
LightOnly = 1, // meant to be used if the component should always use the dark state
DarkOnly = 2 // meant to be used if the component should always use the light state
};
Example:
- The component that switches the windows app theme is set to
Mode.Switch
if the user wants to have the windows app theme switch on a day/night trigger. - When it is set to
Mode.DarkOnly
, it will call its logic to switch to dark whenever necessary - When it is set to
Mode.LightOnly
, it will call its logic to switch to light whenever necessary
A really simple example of a ComponentNeedsUpdate
that makes use of this convention:
private Theme currentComponentTheme;
...
public override bool ComponentNeedsUpdate(Theme newTheme)
{
if (Settings.Component.Mode == Mode.DarkOnly && currentComponentTheme != Theme.Dark)
{
return true;
}
else if (Settings.Component.Mode == Mode.LightOnly && currentComponentTheme != Theme.Light)
{
return true;
}
else if (Settings.Component.Mode == Mode.Switch && currentComponentTheme != newTheme)
{
return true;
}
return false;
}
For all other settings, you are free to create whatever your settings .cs file. You can add classes there if necessary and init them inside your custom settings object. However, a settings object must not have public methods, only properties! All public properties will get serialized and deserialized.
The settings object can be reached in the component via Settings.Component.{whatever properties you have defined}
Open the AutoDarkModeConfig\AdmConfig.cs
file
Add your component to the Addons object like so:
public class Addons
{
public Addons()
{
MyComponentSwitchSettings = new BaseSettings<MyComponentSwitchSettings>();
}
//put your custom settings here!
public BaseSettings<MyComponentSwitchSettings> MyComponentSwitch { get; set; }
}
You can now add your component to the component manager by opening AutoDarkModeSvc\Core\ComponentManager.cs
You will need to register it in three places.
- Create a private readonly field with the component's interface
ISwitchComponent
and initialize it as a field
...
//Others!
private readonly ISwitchComponent AppsSwitch = new AppsSwitch();
private readonly ISwitchComponent ColorFilterSwitch = new ColorFilterSwitch();
...
//Yours!
private readonly ISwitchComponent MyComponentSwitch = new MyComponentSwitchSwitch();
- Add it to the UpdateSettings method
public void UpdateSettings()
{
//you will also find other components here like
AppsSwitch.UpdateSettingsState(Builder.Config.AppsSwitch);
//Add yours below, this is mandatory to receive updates when the settings change
//Don't forget the Addons subclass!
MyComponentSwitch.UpdateSettingsState(Builder.Config.Addons.MyComponentSwitch);
}
- Add it to the list of enabled components
ComponentManager()
{
Builder = AdmConfigBuilder.Instance();
Components = new List<ISwitchComponent>
{
AppsSwitch,
...
//Yours here
MyComponentSwitch
};
UpdateSettings();
}
That's it, your component is now registered. Please check if it updates correctly and works without errors before submitting it!