From 0fd91e18fcd556bb3f61b92d3b8ef15847963ba1 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sun, 24 Mar 2024 21:37:06 +0700 Subject: [PATCH] sidebar: add volume mixer --- .../modules/.commonwidgets/cairo_slider.js | 49 ++++++++ .../modules/.commonwidgets/tabcontainer.js | 105 ++++++++++++++++++ .../modules/.configuration/user_options.js | 4 + .../modules/.widgethacks/advancedrevealers.js | 20 ++-- .config/ags/modules/sideleft/apiwidgets.js | 2 +- .../{ => centermodules}/notificationlist.js | 39 ++++--- .../sideright/centermodules/volumemixer.js | 94 ++++++++++++++++ .config/ags/modules/sideright/sideright.js | 49 +++++++- .config/ags/scss/_common.scss | 30 +++-- .config/ags/scss/_lib_mixins.scss | 1 + .config/ags/scss/_sidebars.scss | 48 ++++++-- .config/ags/scss/main.scss | 2 +- 12 files changed, 401 insertions(+), 42 deletions(-) create mode 100644 .config/ags/modules/.commonwidgets/cairo_slider.js rename .config/ags/modules/sideright/{ => centermodules}/notificationlist.js (80%) create mode 100644 .config/ags/modules/sideright/centermodules/volumemixer.js diff --git a/.config/ags/modules/.commonwidgets/cairo_slider.js b/.config/ags/modules/.commonwidgets/cairo_slider.js new file mode 100644 index 000000000..eff9085c3 --- /dev/null +++ b/.config/ags/modules/.commonwidgets/cairo_slider.js @@ -0,0 +1,49 @@ +import Widget from 'resource:///com/github/Aylur/ags/widget.js'; +const { Gtk } = imports.gi; +const Lang = imports.lang; + +export const AnimatedSlider = ({ + className, + value, + ...rest +}) => { + return Widget.DrawingArea({ + className: `${className}`, + setup: (self) => { + self.connect('draw', Lang.bind(self, (self, cr) => { + const styleContext = self.get_style_context(); + const allocatedWidth = self.get_allocated_width(); + const allocatedHeight = self.get_allocated_height(); + console.log(allocatedHeight, allocatedWidth) + const minWidth = styleContext.get_property('min-width', Gtk.StateFlags.NORMAL); + const minHeight = styleContext.get_property('min-height', Gtk.StateFlags.NORMAL); + const radius = styleContext.get_property('border-radius', Gtk.StateFlags.NORMAL); + const bg = styleContext.get_property('background-color', Gtk.StateFlags.NORMAL); + const fg = styleContext.get_property('color', Gtk.StateFlags.NORMAL); + const value = styleContext.get_property('font-size', Gtk.StateFlags.NORMAL) / 100; + self.set_size_request(-1, minHeight); + const width = allocatedHeight; + const height = minHeight; + + cr.arc(radius, radius, radius, -1 * Math.PI, -0.5 * Math.PI); // Top-left + cr.arc(width - radius, radius, radius, -0.5 * Math.PI, 0); // Top-right + cr.arc(width - radius, height - radius, radius, 0, 0.5 * Math.PI); // Bottom-left + cr.arc(radius, height - radius, radius, 0.5 * Math.PI, 1 * Math.PI); // Bottom-right + cr.setSourceRGBA(bg.red, bg.green, bg.blue, bg.alpha); + cr.closePath(); + cr.fill(); + + // const valueWidth = width * value; + // cr.arc(radius, radius, radius, -1 * Math.PI, -0.5 * Math.PI); // Top-left + // cr.arc(valueWidth - radius, radius, radius, -0.5 * Math.PI, 0); // Top-right + // cr.arc(valueWidth - radius, height - radius, radius, 0, 0.5 * Math.PI); // Bottom-left + // cr.arc(radius, height - radius, radius, 0.5 * Math.PI, 1 * Math.PI); // Bottom-right + // cr.setSourceRGBA(fg.red, fg.green, fg.blue, fg.alpha); + // cr.closePath(); + // cr.fill(); + + })); + }, + ...rest, + }) +} diff --git a/.config/ags/modules/.commonwidgets/tabcontainer.js b/.config/ags/modules/.commonwidgets/tabcontainer.js index f7f7f55e5..f30fadf09 100644 --- a/.config/ags/modules/.commonwidgets/tabcontainer.js +++ b/.config/ags/modules/.commonwidgets/tabcontainer.js @@ -4,6 +4,7 @@ const { Box, Button, EventBox, Label, Overlay, Stack } = Widget; import { MaterialIcon } from './materialicon.js'; import { NavigationIndicator } from './cairo_navigationindicator.js'; import { setupCursorHover } from '../.widgetutils/cursorhover.js'; +import { DoubleRevealer } from '../.widgethacks/advancedrevealers.js'; export const TabContainer = ({ icons, names, children, className = '', setup = () => { }, ...rest }) => { const shownIndex = Variable(0); @@ -167,3 +168,107 @@ export const IconTabContainer = ({ return mainBox; } + +export const ExpandingIconTabContainer = ({ + icons, names, children, className = '', + setup = () => { }, onChange = () => { }, + tabsHpack = 'center', tabSwitcherClassName = '', + ...rest +}) => { + const shownIndex = Variable(0); + let previousShownIndex = 0; + const count = Math.min(icons.length, names.length, children.length); + const tabs = Box({ + hpack: tabsHpack, + className: `spacing-h-5 ${tabSwitcherClassName}`, + children: icons.map((icon, i) => { + const tabIcon = MaterialIcon(icon, 'norm', { hexpand: true }); + const tabName = DoubleRevealer({ + transition1: 'slide_right', + transition2: 'crossfade', + duration1: 0, + duration2: 0, + // duration1: userOptions.animations.durationSmall, + // duration2: userOptions.animations.durationSmall, + child: Label({ + className: 'margin-left-5 txt-small', + label: names[i], + }), + revealChild: i === shownIndex.value, + }) + const button = Button({ + className: 'tab-icon-expandable', + tooltipText: names[i], + child: Box({ + homogeneous: true, + children: [Box({ + hpack: 'center', + children: [ + tabIcon, + tabName, + ] + })], + }), + setup: setupCursorHover, + onClicked: () => shownIndex.value = i, + }); + button.toggleFocus = (value) => { + tabIcon.hexpand = !value; + button.toggleClassName('tab-icon-expandable-active', value); + tabName.toggleRevealChild(value); + } + return button; + }), + setup: (self) => self.hook(shownIndex, (self) => { + self.children[previousShownIndex].toggleFocus(false); + self.children[shownIndex.value].toggleFocus(true); + previousShownIndex = shownIndex.value; + }), + }); + const tabSection = Box({ + homogeneous: true, + children: [EventBox({ + onScrollUp: () => mainBox.prevTab(), + onScrollDown: () => mainBox.nextTab(), + child: Box({ + vertical: true, + hexpand: true, + children: [ + tabs, + ] + }) + })] + }); + const contentStack = Stack({ + transition: 'slide_left_right', + children: children.reduce((acc, currentValue, index) => { + acc[index] = currentValue; + return acc; + }, {}), + setup: (self) => self.hook(shownIndex, (self) => { + self.shown = `${shownIndex.value}`; + }), + }); + const mainBox = Box({ + attribute: { + children: children, + shown: shownIndex, + names: names, + }, + vertical: true, + className: `spacing-v-5 ${className}`, + setup: (self) => { + self.pack_start(tabSection, false, false, 0); + self.pack_end(contentStack, true, true, 0); + setup(self); + self.hook(shownIndex, (self) => onChange(self, shownIndex.value)); + }, + ...rest, + }); + mainBox.nextTab = () => shownIndex.value = Math.min(shownIndex.value + 1, count - 1); + mainBox.prevTab = () => shownIndex.value = Math.max(shownIndex.value - 1, 0); + mainBox.cycleTab = () => shownIndex.value = (shownIndex.value + 1) % count; + mainBox.shown = shownIndex; + + return mainBox; +} diff --git a/.config/ags/modules/.configuration/user_options.js b/.config/ags/modules/.configuration/user_options.js index cf19b33f1..c5f799837 100644 --- a/.config/ags/modules/.configuration/user_options.js +++ b/.config/ags/modules/.configuration/user_options.js @@ -89,6 +89,10 @@ let configOptions = { 'nextTab': "Page_Down", 'prevTab': "Page_Up", }, + 'options': { // Right sidebar + 'nextTab': "Page_Down", + 'prevTab': "Page_Up", + }, 'pin': "Ctrl+p", 'cycleTab': "Ctrl+Tab", 'nextTab': "Ctrl+Page_Down", diff --git a/.config/ags/modules/.widgethacks/advancedrevealers.js b/.config/ags/modules/.widgethacks/advancedrevealers.js index 03e131001..3f1279314 100644 --- a/.config/ags/modules/.widgethacks/advancedrevealers.js +++ b/.config/ags/modules/.widgethacks/advancedrevealers.js @@ -65,16 +65,22 @@ export const DoubleRevealer = ({ revealChild, ...rest }) => { - return Revealer({ + const r2 = Revealer({ + transition: transition2, + transitionDuration: duration2, + revealChild: revealChild, + child: child, + }); + const r1 = Revealer({ transition: transition1, transitionDuration: duration1, revealChild: revealChild, - child: Revealer({ - transition: transition2, - transitionDuration: duration2, - revealChild: revealChild, - child: child, - }), + child: r2, ...rest, }) + r1.toggleRevealChild = (value) => { + r1.revealChild = value; + r2.revealChild = value; + } + return r1; } diff --git a/.config/ags/modules/sideleft/apiwidgets.js b/.config/ags/modules/sideleft/apiwidgets.js index 4b624d651..668cb70cf 100644 --- a/.config/ags/modules/sideleft/apiwidgets.js +++ b/.config/ags/modules/sideleft/apiwidgets.js @@ -185,7 +185,7 @@ const apiCommandStack = Stack({ }) export const apiContentStack = IconTabContainer({ - tabSwitcherClassName: 'sidebar-chat-apiswitcher', + tabSwitcherClassName: 'sidebar-icontabswitcher', className: 'margin-top-5', iconWidgets: APIS.map((api) => api.tabIcon), names: APIS.map((api) => api.name), diff --git a/.config/ags/modules/sideright/notificationlist.js b/.config/ags/modules/sideright/centermodules/notificationlist.js similarity index 80% rename from .config/ags/modules/sideright/notificationlist.js rename to .config/ags/modules/sideright/centermodules/notificationlist.js index 7d2200f65..47e0cc88c 100644 --- a/.config/ags/modules/sideright/notificationlist.js +++ b/.config/ags/modules/sideright/centermodules/notificationlist.js @@ -4,9 +4,9 @@ import Widget from 'resource:///com/github/Aylur/ags/widget.js'; import Notifications from 'resource:///com/github/Aylur/ags/service/notifications.js'; const { Box, Button, Label, Scrollable, Stack } = Widget; -import { MaterialIcon } from '../.commonwidgets/materialicon.js'; -import { setupCursorHover } from '../.widgetutils/cursorhover.js'; -import Notification from '../.commonwidgets/notification.js'; +import { MaterialIcon } from '../../.commonwidgets/materialicon.js'; +import { setupCursorHover } from '../../.widgetutils/cursorhover.js'; +import Notification from '../../.commonwidgets/notification.js'; export default (props) => { const notifEmptyContent = Box({ @@ -18,7 +18,7 @@ export default (props) => { children: [ Box({ vertical: true, - className: 'spacing-v-5', + className: 'spacing-v-5 txt-subtext', children: [ MaterialIcon('notifications_active', 'gigantic'), Label({ label: 'No notifications', className: 'txt-small' }), @@ -88,18 +88,29 @@ export default (props) => { Notifications.clear(); notificationList.get_children().forEach(ch => ch.attribute.destroyWithAnims()) }); + const notifCount = Label({ + attribute: { + updateCount: (self) => { + const count = Notifications.notifications.length; + if (count > 0) self.label = `${count} notifications`; + else self.label = ''; + }, + }, + hexpand: true, + xalign: 0, + className: 'txt-small margin-left-10', + label: `${Notifications.notifications.length}`, + setup: (self) => self + .hook(Notifications, (box, id) => self.attribute.updateCount(self), 'notified') + .hook(Notifications, (box, id) => self.attribute.updateCount(self), 'dismissed') + .hook(Notifications, (box, id) => self.attribute.updateCount(self), 'closed') + , + }); const listTitle = Box({ vpack: 'start', className: 'sidebar-group-invisible txt spacing-h-5', children: [ - Label({ - hexpand: true, - xalign: 0, - className: 'txt-title-small margin-left-10', - // ^ (extra margin on the left so that it looks similarly spaced - // when compared to borderless "Clear" button on the right) - label: 'Notifications', - }), + notifCount, silenceButton, clearButton, ] @@ -131,11 +142,11 @@ export default (props) => { }); return Box({ ...props, - className: 'sidebar-group spacing-v-5', + className: 'spacing-v-5', vertical: true, children: [ - listTitle, listContents, + listTitle, ] }); } diff --git a/.config/ags/modules/sideright/centermodules/volumemixer.js b/.config/ags/modules/sideright/centermodules/volumemixer.js new file mode 100644 index 000000000..58c7f82f0 --- /dev/null +++ b/.config/ags/modules/sideright/centermodules/volumemixer.js @@ -0,0 +1,94 @@ +// This file is for the notification list on the sidebar +// For the popup notifications, see onscreendisplay.js +// The actual widget for each single notification is in ags/modules/.commonwidgets/notification.js +import Widget from 'resource:///com/github/Aylur/ags/widget.js'; +import Audio from 'resource:///com/github/Aylur/ags/service/audio.js'; +const { Box, Button, Icon, Label, Scrollable, Slider, Stack } = Widget; +import { MaterialIcon } from '../../.commonwidgets/materialicon.js'; +import { setupCursorHover } from '../../.widgetutils/cursorhover.js'; +import { AnimatedSlider } from '../../.commonwidgets/cairo_slider.js'; + +const appVolume = (stream) => { + console.log(stream) + return Box({ + className: 'sidebar-volmixer-stream spacing-h-10', + children: [ + Icon({ + className: 'sidebar-volmixer-stream-appicon', + vpack: 'center', + icon: stream.stream.name.toLowerCase(), + }), + Box({ + hexpand: true, + vpack: 'center', + vertical: true, + className: 'spacing-v-5', + children: [ + Label({ + xalign: 0, + maxWidthChars: 10, + truncate: 'end', + label: stream.description, + className: 'txt-small', + setup: (self) => { + self.hook(stream, (self) => { + self.label = `${stream.description}` + }) + } + }), + Slider({ + drawValue: false, + hpack: 'fill', + className: 'sidebar-volmixer-stream-slider', + value: stream.volume, + min: 0, max: 1, + onChange: ({value}) => { + stream.volume = value; + }, + setup: (self) => { + self.hook(stream, (self) => { + self.value = stream.volume; + }) + } + }), + // Box({ + // homogeneous: true, + // className: 'test', + // children: [AnimatedSlider({ + // className: 'sidebar-volmixer-stream-slider', + // value: stream.volume, + // })], + // }) + ] + }) + ] + }) +} + +export default (props) => { + const appList = Scrollable({ + vexpand: true, + child: Box({ + attribute: { + 'updateStreams': (self) => { + const streams = Audio.apps; + self.children = streams.map(stream => appVolume(stream)); + }, + }, + vertical: true, + className: 'spacing-v-5', + setup: (self) => self + .hook(Audio, self.attribute.updateStreams, 'stream-added') + .hook(Audio, self.attribute.updateStreams, 'stream-removed') + , + }) + }) + return Box({ + ...props, + className: 'spacing-v-5', + vertical: true, + children: [ + appList, + ] + }); +} diff --git a/.config/ags/modules/sideright/sideright.js b/.config/ags/modules/sideright/sideright.js index b08ad91f5..be9f9bb25 100644 --- a/.config/ags/modules/sideright/sideright.js +++ b/.config/ags/modules/sideright/sideright.js @@ -15,9 +15,26 @@ import { ModulePowerIcon, ModuleRawInput } from "./quicktoggles.js"; -import ModuleNotificationList from "./notificationlist.js"; +import ModuleNotificationList from "./centermodules/notificationlist.js"; +import ModuleVolumeMixer from "./centermodules/volumemixer.js"; import { ModuleCalendar } from "./calendar.js"; import { getDistroIcon } from '../.miscutils/system.js'; +import { MaterialIcon } from '../.commonwidgets/materialicon.js'; +import { ExpandingIconTabContainer } from '../.commonwidgets/tabcontainer.js'; +import { checkKeybind } from '../.widgetutils/keybind.js'; + +const centerWidgets = [ + { + name: 'Notifications', + materialIcon: 'notifications', + contentWidget: ModuleNotificationList(), + }, + { + name: 'Volume mixer', + materialIcon: 'volume_up', + contentWidget: ModuleVolumeMixer(), + }, +]; const timeRow = Box({ className: 'spacing-h-10 sidebar-group-invisible-morehorizpad', @@ -60,6 +77,17 @@ const togglesBox = Widget.Box({ ] }) +export const sidebarOptionsStack = ExpandingIconTabContainer({ + tabsHpack: 'center', + tabSwitcherClassName: 'sidebar-icontabswitcher', + icons: centerWidgets.map((api) => api.materialIcon), + names: centerWidgets.map((api) => api.name), + children: centerWidgets.map((api) => api.contentWidget), + onChange: (self, id) => { + self.shown = centerWidgets[id].name; + } +}); + export default () => Box({ vexpand: true, hexpand: true, @@ -84,9 +112,24 @@ export default () => Box({ togglesBox, ] }), - ModuleNotificationList({ vexpand: true, }), + Box({ + className: 'sidebar-group', + children: [ + sidebarOptionsStack, + ], + }), ModuleCalendar(), ] }), - ] + ], + setup: (self) => self + .on('key-press-event', (widget, event) => { // Handle keybinds + if (checkKeybind(event, userOptions.keybinds.sidebar.options.nextTab)) { + sidebarOptionsStack.nextTab(); + } + else if (checkKeybind(event, userOptions.keybinds.sidebar.options.prevTab)) { + sidebarOptionsStack.prevTab(); + } + }) + , }); diff --git a/.config/ags/scss/_common.scss b/.config/ags/scss/_common.scss index 435837e52..2d3376e66 100644 --- a/.config/ags/scss/_common.scss +++ b/.config/ags/scss/_common.scss @@ -1,7 +1,3 @@ -// * { -// border: 1px solid $onSurfaceVariant; // Debugging -// } - * { selection { background-color: $secondary; @@ -197,8 +193,7 @@ popover { color: $onSecondaryContainer; } -.multipleselection-container { -} +.multipleselection-container {} .multipleselection-btn { @include small-rounding; @@ -277,8 +272,8 @@ popover { .tab-icon { @include element_decel; @include full-rounding; - min-width: 2.182rem; - min-height: 2.182rem; + min-width: 2.25rem; + min-height: 2.25rem; font-size: 1.406rem; color: $onSurface; } @@ -288,6 +283,23 @@ popover { color: $onSecondaryContainer; } +.tab-icon-expandable { + transition: 0ms; + @include full-rounding; + min-width: 2.25rem; + min-height: 2.25rem; + font-size: 1.406rem; + color: $onSurface; + padding: 0rem; +} + +.tab-icon-expandable-active { + background-color: $secondaryContainer; + color: $onSecondaryContainer; + padding: 0rem 0.545rem; + min-width: 9.545rem; +} + widget { @include small-rounding; -} +} \ No newline at end of file diff --git a/.config/ags/scss/_lib_mixins.scss b/.config/ags/scss/_lib_mixins.scss index d96751396..e3f2dc251 100644 --- a/.config/ags/scss/_lib_mixins.scss +++ b/.config/ags/scss/_lib_mixins.scss @@ -1,6 +1,7 @@ // Common colors $hovercolor: $surfaceContainerHigh; $activecolor: $surfaceContainerHighest; +$rounding_verysmall: 0.477rem; $rounding_small: 0.818rem; $rounding_mediumsmall: 0.955rem; $rounding_medium: 1.159rem; diff --git a/.config/ags/scss/_sidebars.scss b/.config/ags/scss/_sidebars.scss index c590b127c..ca2313cb8 100644 --- a/.config/ags/scss/_sidebars.scss +++ b/.config/ags/scss/_sidebars.scss @@ -260,7 +260,7 @@ $sidebar_chat_textboxareaColor: mix($onSurfaceVariant, $surfaceVariant, 40%); @include full-rounding; @include element_decel; padding: 0rem 0.682rem; - background-color: $surfaceContainerHigh; + background-color: $layer2; color: $onSurface; } @@ -278,7 +278,7 @@ $sidebar_chat_textboxareaColor: mix($onSurfaceVariant, $surfaceVariant, 40%); @include element_decel; min-width: 2.045rem; min-height: 2.045rem; - background-color: $surfaceContainerHigh; + background-color: $layer2; color: $outline; } @@ -326,7 +326,7 @@ $sidebar_chat_textboxareaColor: mix($onSurfaceVariant, $surfaceVariant, 40%); .sidebar-todo-new { @include full-rounding; @include element_decel; - background-color: $surfaceContainerHigh; + background-color: $layer2; color: $onSurfaceVariant; margin: 0.341rem; padding: 0.205rem 0.545rem; @@ -492,7 +492,7 @@ $colorpicker_rounding: 0.341rem; padding: 0.341rem; } -.sidebar-chat-apiswitcher { +.sidebar-icontabswitcher { @include full-rounding; @include group-padding; background-color: $layer1; @@ -517,7 +517,7 @@ $colorpicker_rounding: 0.341rem; .sidebar-chat-textarea { @include normal-rounding; - background-color: $surfaceContainerHigh; + background-color: $layer1; color: $onSurfaceVariant; padding: 0.682rem; } @@ -709,7 +709,7 @@ $colorpicker_rounding: 0.341rem; .sidebar-chat-chip-action { @include element_decel; - background-color: $surfaceContainerHigh; + background-color: $layer2; color: $onSurfaceVariant; } @@ -731,7 +731,7 @@ $colorpicker_rounding: 0.341rem; @include element_decel; @include small-rounding; padding: 0.341rem 0.477rem; - background-color: $surfaceContainerHigh; + background-color: $layer3; color: $onSurfaceVariant; } @@ -845,3 +845,37 @@ $waifu_image_overlay_transparency: 0.7; @include element_decel; margin: 0.545rem; } + +.sidebar-volmixer-stream { + // @include normal-rounding; + // background-color: $layer2; + // color: $onLayer2; + border-bottom: 0.068rem solid $outlineVariant; + padding: 0.682rem; +} + +.sidebar-volmixer-stream-appicon { + font-size: 3.273rem; +} + +.sidebar-volmixer-stream-slider { + trough { + border-radius: $rounding_verysmall; + min-height: 1.364rem; + min-width: 1.364rem; + background-color: $secondaryContainer; + } + + highlight { + border-radius: $rounding_verysmall; + min-height: 1.364rem; + min-width: 1.364rem; + background-color: $primary; + } + + slider { + border-radius: $rounding_verysmall; + min-height: 1.364rem; + min-width: 1.364rem; + } +} \ No newline at end of file diff --git a/.config/ags/scss/main.scss b/.config/ags/scss/main.scss index ea1f378fe..990d1d6a8 100644 --- a/.config/ags/scss/main.scss +++ b/.config/ags/scss/main.scss @@ -38,4 +38,4 @@ border-radius: 0rem; border-bottom-right-radius: $rounding_large; border: 0rem solid; -} \ No newline at end of file +}