diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 20ef696d7..2b39d7b63 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -23,7 +23,7 @@ jobs: - name: Install Dependencies run: | apt update - apt install -y meson gobject-introspection libgee-0.8-dev libgirepository1.0-dev libgtk-4-dev valac + apt install -y meson gobject-introspection libgee-0.8-dev libgirepository1.0-dev libgtk-4-dev sassc valac - name: Build env: DESTDIR: out diff --git a/README.md b/README.md index 13c7df2b5..245652500 100644 --- a/README.md +++ b/README.md @@ -10,11 +10,12 @@ built for elementary OS. ## Building, Testing, and Installation You'll need the following dependencies: -* meson >= 0.48.2 +* meson >= 0.49.0 * gobject-introspection * libgee-0.8-dev * libgirepository1.0-dev * libgtk-4-dev >= 4.4.0 +* sassc * valac Run `meson build` to configure the build environment: diff --git a/lib/Init.vala b/lib/Init.vala index 7e95da0e2..57ede139b 100644 --- a/lib/Init.vala +++ b/lib/Init.vala @@ -5,6 +5,7 @@ namespace Granite { private static bool initialized = false; + private static Gtk.CssProvider css_provider = null; /* * Initializes Granite. @@ -20,8 +21,24 @@ namespace Granite { typeof (Granite.Settings).ensure (); + unowned var display_manager = Gdk.DisplayManager.@get (); + display_manager.display_opened.connect (register_display); + + foreach (unowned var display in display_manager.list_displays ()) { + register_display (display); + } + GLib.Intl.bindtextdomain (Granite.GETTEXT_PACKAGE, Granite.LOCALEDIR); GLib.Intl.bind_textdomain_codeset (Granite.GETTEXT_PACKAGE, "UTF-8"); initialized = true; } + + private static void register_display (Gdk.Display display) { + if (css_provider == null) { + css_provider = new Gtk.CssProvider (); + css_provider.load_from_resource ("/io/elementary/granite/Granite.css"); + } + + Gtk.StyleContext.add_provider_for_display (display, css_provider, Gtk.STYLE_PROVIDER_PRIORITY_THEME); + } } diff --git a/lib/Styles/Dialog.scss b/lib/Styles/Dialog.scss new file mode 100644 index 000000000..18d02d018 --- /dev/null +++ b/lib/Styles/Dialog.scss @@ -0,0 +1,26 @@ +window.csd.dialog { + border-radius: rem(6px); + + .dialog-content-area { + margin: rem(12px); + margin-top: rem(18px); + } + + .dialog-action-area { + margin: rem(12px); + // Double space between content and actions + margin-top: rem(16px); + + button.text-button { + min-width: rem(60px); + } + + button:dir(ltr) + button { + margin-left: rem(6px); + } + + button:dir(rtl) + button { + margin-right: rem(6px); + } + } +} diff --git a/lib/Styles/Header.scss b/lib/Styles/Header.scss new file mode 100644 index 000000000..d2e270bb0 --- /dev/null +++ b/lib/Styles/Header.scss @@ -0,0 +1,17 @@ +header { + &:not(:first-child) { + margin-top: 1em; + } + + .heading { + font-weight: 700; + opacity: 0.9; + padding: 0; + } + + .dim-label { + font-size: 0.9em; + margin-top: 0.1em; + opacity: 0.8; + } +} diff --git a/lib/Styles/Index.scss b/lib/Styles/Index.scss new file mode 100644 index 000000000..f4d83e84d --- /dev/null +++ b/lib/Styles/Index.scss @@ -0,0 +1,29 @@ +@function rem($pixels, $text-size: 9pt) { + @if (unitless($pixels)) { + $pixels: $pixels * 1px; + } + + @if (unitless($text-size)) { + $text-size: $text-size * 1px; + } + + @if ($pixels > 0) { + // Workaround GTK clamping instead of rounding up + @return ($pixels / $text-size * 1rem) + 0.000000001rem; + } @else { + // Workaround GTK clamping instead of rounding up + @return ($pixels / $text-size * 1rem) - 0.000000001rem; + } +} + +// Class constants and common styles +@import '_button.scss'; +@import '_label.scss'; + +// Granite widgets +@import 'Dialog.scss'; +@import 'Header.scss'; +@import 'MessageDialog.scss'; +@import 'OverlayBar.scss'; +@import 'Placeholder.scss'; +@import 'Toast.scss'; diff --git a/lib/Styles/MessageDialog.scss b/lib/Styles/MessageDialog.scss new file mode 100644 index 000000000..4218d1c75 --- /dev/null +++ b/lib/Styles/MessageDialog.scss @@ -0,0 +1,10 @@ +window.csd.dialog.message { + .dialog-vbox .title { + font-weight: 500; + font-size: 1.22em + } + + .dialog-content-area box + expander-widget { + margin-bottom: rem(6px); + } +} diff --git a/lib/Styles/OverlayBar.scss b/lib/Styles/OverlayBar.scss new file mode 100644 index 000000000..c90c1a8ec --- /dev/null +++ b/lib/Styles/OverlayBar.scss @@ -0,0 +1,17 @@ +overlaybar .osd { + border-radius: rem(3px); + padding: rem(6px) rem(12px); + + spinner { + text-shadow: 0 rem(1px) rem(2px) rgba(black, 0.6); + -gtk-icon-shadow: 0 rem(1px) rem(2px) rgba(black, 0.6); + } + + revealer:dir(ltr) > spinner { + margin-left: rem(6px); + } + + revealer:dir(rtl) > spinner { + margin-right: rem(6px); + } +} diff --git a/lib/Styles/Placeholder.scss b/lib/Styles/Placeholder.scss new file mode 100644 index 000000000..dcf9914a9 --- /dev/null +++ b/lib/Styles/Placeholder.scss @@ -0,0 +1,70 @@ +placeholder { + margin: rem(12px); + + > grid > .large-icons { + -gtk-icon-size: 48px; + + &:dir(ltr) { + margin-right: rem(3px); + } + + &:dir(rtl) { + margin-left: rem(3px); + } + } + + .title-1:dir(ltr), + .title-2:dir(ltr) { + margin-left: rem(9px); + } + + .title-1:dir(rtl), + .title-2:dir(rtl) { + margin-right: rem(9px); + } + + .title-1 + .title-2 { + margin-top: rem(3px); + } + + .title-2 { + @extend .dim-label; + + color: #{'@theme_fg_color'}; + font-size: 12pt; + font-weight: initial; + } + + box { + margin-top: rem(12px); + + button { + .large-icons { + -gtk-icon-size: 32px; + + &:dir(ltr) { + margin-right: rem(6px); + } + + &:dir(rtl) { + margin-left: rem(6px); + } + } + + + label { + &.title-3 { + font-weight: initial; + } + + &:not(.title-3) { + @extend .dim-label; + } + } + + + button { + margin-top: rem(12px); + } + } + } +} diff --git a/lib/Styles/Toast.scss b/lib/Styles/Toast.scss new file mode 100644 index 000000000..7fb15d38d --- /dev/null +++ b/lib/Styles/Toast.scss @@ -0,0 +1,4 @@ +toast .osd > label { + margin: 0 rem(12px); + text-shadow: 0 1px 2px #{'alpha(@theme_fg_color, 0.6)'}; +} diff --git a/lib/Styles/_button.scss b/lib/Styles/_button.scss new file mode 100644 index 000000000..de8d76465 --- /dev/null +++ b/lib/Styles/_button.scss @@ -0,0 +1,43 @@ +button { + // Stopgap since we can't do angled buttons in GtkCSS, and generating all + // the necessary SVGs for light/dark and accent color combinations is + // untenable. Ideally we'd deprecate this in favor of something like a + // Granite.BackButton with custom drawing; until then, stick an icon in it. + &.back-button { + background-repeat: no-repeat no-repeat; + background-size: 16px, cover; + + &:dir(ltr) { + background-image: + -gtk-icontheme('go-previous-symbolic'), + linear-gradient( + to bottom, + #{'alpha(@highlight_color, 0.2)'}, + rgba(white, 0) + ); + padding-left: calc(#{rem(9px)} + 16px); + background-position: + #{rem(6px)} 50%, + center, center; + } + + &:dir(rtl) { + background-image: + -gtk-icontheme('go-next-symbolic'), + linear-gradient( + to bottom, + #{'alpha(@highlight_color, 0.2)'}, + rgba(white, 0) + ); + padding-right: calc(#{rem(9px)} + 16px); + background-position: + calc(100% - #{rem(6px)}) 50%, + center, center; + } + } + + &.circular { + // Not 50% because that creates a squished ellipse for non-squares widgets + border-radius: 9999px; + } +} diff --git a/lib/Styles/_label.scss b/lib/Styles/_label.scss new file mode 100644 index 000000000..00f5ea103 --- /dev/null +++ b/lib/Styles/_label.scss @@ -0,0 +1,31 @@ +.title-1 { + font-size: 24pt; + font-weight: 700; + letter-spacing: -0.04em; +} + +.title-2 { + font-weight: 300; + font-size: 18pt; + letter-spacing: -0.05em; +} + +.title-3 { + font-size: 11pt; +} + +.title-4 { + font-weight: 700; + opacity: 0.8; + padding-bottom: 0.5em; + padding-top: 0.5em; +} + +.dim-label { + opacity: 0.75; +} + +// Intended to match the Pango size of `` and `size='smaller'` +.small-label { + font-size: 0.85em; +} diff --git a/lib/Styles/meson.build b/lib/Styles/meson.build new file mode 100644 index 000000000..be0d2cab7 --- /dev/null +++ b/lib/Styles/meson.build @@ -0,0 +1,25 @@ +sassc = find_program('sassc') + +sassc_opts = [ '-a', '-M', '-t', 'compact' ] + +stylesheet_deps = custom_target( + 'Granite.scss', + input: 'Index.scss', + output: 'Granite.css', + command: [ + sassc, + sassc_opts, + '@INPUT@', + '@OUTPUT@', + ] +) + +stylesheet_resource = gnome.compile_resources( + 'styles-resource', + 'styles.gresource.xml', + source_dir: [ + meson.current_build_dir(), + meson.current_source_dir(), + ], + dependencies: stylesheet_deps +) diff --git a/lib/Styles/styles.gresource.xml b/lib/Styles/styles.gresource.xml new file mode 100644 index 000000000..06a937a08 --- /dev/null +++ b/lib/Styles/styles.gresource.xml @@ -0,0 +1,6 @@ + + + + Granite.css + + diff --git a/lib/meson.build b/lib/meson.build index 5cd1e4f9d..6882c8d08 100644 --- a/lib/meson.build +++ b/lib/meson.build @@ -1,3 +1,5 @@ +subdir('Styles') + libgranite_sources = files( 'DateTime.vala', 'Constants.vala', @@ -59,6 +61,7 @@ libgranite = library( meson.project_name(), libgranite_sources, + stylesheet_resource, config_vala, dependencies: [ @@ -118,4 +121,3 @@ granite_pc = pkgconfig.generate( version: meson.project_version(), url: 'https://github.com/elementary/granite', ) - diff --git a/meson.build b/meson.build index e71175295..882025654 100644 --- a/meson.build +++ b/meson.build @@ -1,7 +1,7 @@ project( 'granite-7', 'vala', 'c', - meson_version: '>= 0.48.2', + meson_version: '>= 0.49.0', version: '7.2.0' ) @@ -60,6 +60,7 @@ icons_dir = join_paths( ) pkgconfig = import('pkgconfig') +gnome = import('gnome') i18n = import('i18n') subdir('lib')