Forcing The Dark Color Scheme

GNOME applications will respect the system setting for the light or dark theme. It is possible, however, to present the choice of forcing the dark theme to the user in your application’s UI.

../../../_images/dark_mode.png

Add the “Dark Mode” item to the application’s menu

  1. Open the UI definition file for the TextViewerWindow widget

  2. Add a menu item for the app.dark action, called Dark Mode

<menu id="primary_menu">
  <section>
    <item>
      <attribute name="label" translatable="yes">Save _as...</attribute>
      <attribute name="action">win.save-as</attribute>
    </item>
    <item>
      <attribute name="label" translatable="yes">_Dark mode</attribute>
      <attribute name="action">app.dark-mode</attribute>
    </item>

Add the dark mode action to the application

  1. Open the TextViewApplication source

  2. Find the TextViewApplication instance initialization function

  3. Create the dark-mode stateful action and connect to its activate and change-state signals

  4. Add the action to the application

static void
text_viewer_application_init (TextViewerApplication *self)
{
  g_autoptr (GSimpleAction) dark_action =
    g_simple_action_new_stateful ("dark-mode",
                                  NULL,
                                  g_variant_new_boolean (FALSE));
  g_signal_connect (dark_action, "activate", G_CALLBACK (toggle_dark_mode), self);
  g_signal_connect (dark_action, "change-state", G_CALLBACK (change_color_scheme), self);
  g_action_map_add_action (G_ACTION_MAP (self), G_ACTION (dark_action));
  1. Add the toggle_dark_mode callback; this callback toggles the state of the dark-mode action between “true” and “false”

static void
toggle_dark_mode (GSimpleAction *action,
                  GVariant      *parameter G_GNUC_UNUSED,
                  gpointer       user_data G_GNUC_UNUSED)
{
  GVariant *state = g_action_get_state (G_ACTION (action));
  gboolean old_state = g_variant_get_boolean (state);
  gboolean new_state = !old_state;

  g_action_change_state (G_ACTION (action), g_variant_new_boolean (new_state));

  g_variant_unref (state);
}
  1. Add the change_color_scheme callback; this callback is responsible for switching the application’s color scheme using the AdwStyleManager API

static void
change_color_scheme (GSimpleAction         *action,
                     GVariant              *new_state,
                     TextViewerApplication *self)
{
  gboolean dark_mode = g_variant_get_boolean (new_state);

  AdwStyleManager *style_manager = adw_style_manager_get_default ();

  if (dark_mode)
    adw_style_manager_set_color_scheme (style_manager, ADW_COLOR_SCHEME_FORCE_DARK);
  else
    adw_style_manager_set_color_scheme (style_manager, ADW_COLOR_SCHEME_DEFAULT);

  g_simple_action_set_state (action, new_state);
}

Store the dark mode state as a setting

If you want to preserve the chosen color scheme across sessions you can store it inside GSettings, which you added in Saving The Application State.

Add a new key to the settings schema

  1. Open the com.example.TextViewer.gschema.xml file

  2. Add a dark-mode boolean key

<?xml version="1.0" encoding="UTF-8"?>
<schemalist gettext-domain="text-viewer">
  <schema id="com.example.TextViewer" path="/com/example/TextViewer/">
    <key name="window-width" type="i">
      <default>600</default>
    </key>
    <key name="window-height" type="i">
      <default>400</default>
    </key>
    <key name="window-maximized" type="b">
      <default>false</default>
    </key>
    <key name="dark-mode" type="b">
      <default>false</default>
    </key>
  </schema>
</schemalist>

Add GSettings to the application

  1. Add a GSettings instance to the TextViewerApplication

struct _TextViewerApplication
{
  GtkApplication parent_instance;

  GSettings *settings;
};
  1. Clear the GSettings instance when the TextViewerApplication instance is disposed

static void
text_viewer_application_dispose (GObject *gobject)
{
  TextViewerApplication *self = TEXT_VIEWER_APPLICATION (gobject);

  g_clear_object (&self->settings);

  G_OBJECT_CLASS (text_viewer_application_parent_class)->dispose (gobject);
}

static void
text_viewer_application_class_init (TextViewerApplicationClass *klass)
{
  GApplicationClass *app_class = G_APPLICATION_CLASS (klass);
  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);

  gobject_class->dispose = text_viewer_application_dispose;
  1. Initialize the GSettings instance alongside the rest of the TextViewerApplication

static void
text_viewer_application_init (TextViewerApplication *self)
{
  self->settings = g_settings_new ("com.example.TextViewer");

  g_autoptr (GSimpleAction) quit_action = g_simple_action_new ("quit", NULL);
  g_signal_connect_swapped (quit_action, "activate", G_CALLBACK (g_application_quit), self);
  g_action_map_add_action (G_ACTION_MAP (self), G_ACTION (quit_action));

Set the initial state for the color scheme

  1. Retrieve the value of the dark-mode GSettings key

  2. Set the color scheme using the key’s value

  3. Initialize the state of the dark-mode action with the key’s value

static void
text_viewer_application_init (TextViewerApplication *self)
{
  self->settings = g_settings_new ("com.example.TextViewer");

  gboolean dark_mode = g_settings_get_boolean (self->settings, "dark-mode");
  AdwStyleManager *style_manager = adw_style_manager_get_default ();
  if (dark_mode)
    adw_style_manager_set_color_scheme (style_manager, ADW_COLOR_SCHEME_FORCE_DARK);
  else
    adw_style_manager_set_color_scheme (style_manager, ADW_COLOR_SCHEME_DEFAULT);

  g_autoptr (GSimpleAction) dark_action =
    g_simple_action_new_stateful ("dark-mode", NULL, g_variant_new_boolean (dark_mode));
  g_signal_connect (dark_action, "activate", G_CALLBACK (text_viewer_application_toggle_action), self);
  g_signal_connect (dark_action, "change-state", G_CALLBACK (text_viewer_application_dark_mode_changed), self);
  g_action_map_add_action (G_ACTION_MAP (self), G_ACTION (dark_action));

Save the color scheme when it changes

  1. Update the dark-mode GSettings key using the state of the dark-mode action whenever it changes.

static void
change_color_scheme (GSimpleAction         *action,
                     GVariant              *value,
                     TextViewerApplication *self)
{
  gboolean dark_mode = g_variant_get_boolean (value);

  AdwStyleManager *style_manager = adw_style_manager_get_default ();

  if (dark_mode)
    adw_style_manager_set_color_scheme (style_manager, ADW_COLOR_SCHEME_FORCE_DARK);
  else
    adw_style_manager_set_color_scheme (style_manager, ADW_COLOR_SCHEME_DEFAULT);

  g_simple_action_set_state (action, value);

  g_settings_set_boolean (self->settings, "dark-mode", dark_mode);
}

In this lesson you have learned how to force the dark color scheme for your application, and storing it as an application preference.