diff --git a/config/ags/config.js b/config/ags/config.js index 7b1ec4ba..fc76c657 100644 --- a/config/ags/config.js +++ b/config/ags/config.js @@ -6,6 +6,7 @@ import { NotificationsPopupList } from './js/notifications/popup.js' import { Calendar } from './js/date.js'; import { QuickSettings } from './js/quick-settings/main.js'; import Overview from './js/overview/main.js'; +import AppLauncher from './js/applauncher/main.js'; import { Closer, closeAll } from './js/misc/closer.js'; ags.App.closeAll = () => closeAll(); @@ -27,6 +28,7 @@ export default { 'calendar': 500, 'powermenu': 500, 'overview': 500, + 'applauncher': 500, }, windows: [ Powermenu, @@ -37,5 +39,6 @@ export default { Calendar, QuickSettings, Overview, + AppLauncher, ], }; diff --git a/config/ags/js/applauncher/main.js b/config/ags/js/applauncher/main.js new file mode 100644 index 00000000..6c801603 --- /dev/null +++ b/config/ags/js/applauncher/main.js @@ -0,0 +1,126 @@ +const { App } = ags; +const { Applications } = ags.Service; +const { Label, Box, Icon, Button, Scrollable, Entry, Window } = ags.Widget; + +import { Separator } from '../misc/separator.js'; +import { PopUp } from '../misc/popup.js'; + +const icons = { + apps: { + apps: 'view-app-grid-symbolic', + search: 'preferences-system-search-symbolic', + } +}; + +const AppItem = (app, window) => { + if (app.app.get_string('Icon') == 'Nextcloud') + return; + + return Button({ + className: 'app', + connections: [['clicked', () => { + App.closeWindow(window); + app.launch(); + }]], + child: Box({ + children: [ + Icon({ + icon: app.app.get_string('Icon'), + size: 42, + }), + Box({ + vertical: true, + children: [ + Label({ + className: 'title', + label: app.name, + xalign: 0, + valign: 'center', + ellipsize: 3, + }), + Label({ + className: 'description', + label: app.description || '', + wrap: true, + xalign: 0, + justification: 'left', + valign: 'center', + }), + ], + }), + ], + }), + }); +}; + +const Applauncher = ({ windowName = 'applauncher' } = {}) => { + const list = Box({ vertical: true }); + const placeholder = Label({ + label: " Couldn't find a match", + className: 'placeholder', + }); + const entry = Entry({ + hexpand: true, + placeholderText: 'Search', + onAccept: ({ text }) => { + const list = Applications.query(text); + if (list[0]) { + App.toggleWindow(windowName); + list[0].launch(); + } + }, + onChange: ({ text }) => { + list.children = Applications.query(text).map(app => [ + Separator(4), + AppItem(app, windowName), + ]).flat(); + list.add(Separator(4)); + list.show_all(); + + placeholder.visible = list.children.length === 1; + }, + }); + + return Box({ + className: 'applauncher', + properties: [['list', list]], + vertical: true, + children: [ + Box({ + className: 'header', + children: [ + Icon(icons.apps.search), + entry, + ], + }), + Scrollable({ + hscroll: 'never', + child: Box({ + vertical: true, + children: [list, placeholder], + }), + }), + ], + connections: [[App, (_b, name, visible) => { + if (name !== windowName) + return; + + entry.set_text('-'); // force onChange + entry.set_text(''); + if (visible) + entry.grab_focus(); + }]], + }); +}; + +// TODO: make focusable a bit less focusable +export default Window({ + name: 'applauncher', + popup: true, + focusable: true, + layer: 'overlay', + child: PopUp({ + name: 'applauncher', + child: Applauncher(), + }), +}); diff --git a/config/ags/scss/main.scss b/config/ags/scss/main.scss index a555c9de..457431e8 100644 --- a/config/ags/scss/main.scss +++ b/config/ags/scss/main.scss @@ -13,3 +13,4 @@ @import "./widgets/quick-settings.scss"; @import "./widgets/player.scss"; @import "./widgets/overview.scss"; +@import "./widgets/applauncher.scss"; diff --git a/config/ags/scss/widgets/applauncher.scss b/config/ags/scss/widgets/applauncher.scss new file mode 100644 index 00000000..d9fe80b3 --- /dev/null +++ b/config/ags/scss/widgets/applauncher.scss @@ -0,0 +1,113 @@ +.applauncher { + all: unset; + box-shadow: 0 0 4.5px 0 rgba(0, 0, 0, 0.6); + margin: 9px; + border: 2px solid $contrastbg; + border-radius: 25px; + background-color: $bg; + color: #f8f8f2; + padding: 16.2px; + + * { + font-size: 16px; + } + + .header { + margin: 16.2px; + margin-bottom: 0; + + image, entry { + all: unset; + border-radius: 9px; + color: #f8f8f2; + background-color: rgba(#44475a, 0.6); + border: 1px solid #44475a; + padding: 4.5px; + } + + image { + margin-right: 9px; + -gtk-icon-transform: scale(0.8); + font-size: 25.6px; + } + } + + scrolledwindow { + padding: 16.2px; + min-width: 700px; + min-height: 450px; + + scrollbar, scrollbar * { + all: unset; + } + scrollbar.vertical { + transition: 200ms; + background-color: rgba(23, 23, 23, 0.3); + + &:hover { + background-color: rgba(23, 23, 23, 0.7); + + slider { + background-color: rgba(238, 238, 238, 0.7); + min-width: .6em; + } + } + slider { + background-color: rgba(238, 238, 238, 0.5); + border-radius: 9px; + min-width: .4em; + min-height: 2em; + transition: 200ms; + } + } + } + + .placeholder { + margin-top: 9px; + color: #f8f8f2; + font-size: 1.2em; + } + + .app { + all: unset; + transition: 200ms; + padding: 9px; + + &:active { + background-color: rgba($contrastbg, 0.5); + border-radius: 9px; + box-shadow: inset 0 0 0 3px rgba(238, 238, 238, 0.03); + + .title { + color: #f8f8f2; + } + } + + &:hover, &:focus { + .title { + color: $contrastbg; + } + + image { + -gtk-icon-shadow: 2px 2px $contrastbg; + } + } + + label { + transition: 200ms; + + &.title { + color: #f8f8f2; + } + + &.description { + color: rgba(238, 238, 238, 0.7); + } + } + + image { + transition: 200ms; + margin-right: 9px; + } + } +} diff --git a/config/ags/style.css b/config/ags/style.css index 168561a1..3a9f4382 100644 --- a/config/ags/style.css +++ b/config/ags/style.css @@ -695,3 +695,76 @@ calendar:indeterminate { border: 2px solid #333333; } .overview .special .workspace .window.active { border: 2px solid purple; } + +.applauncher { + all: unset; + box-shadow: 0 0 4.5px 0 rgba(0, 0, 0, 0.6); + margin: 9px; + border: 2px solid rgba(189, 147, 249, 0.8); + border-radius: 25px; + background-color: rgba(40, 42, 54, 0.8); + color: #f8f8f2; + padding: 16.2px; } + .applauncher * { + font-size: 16px; } + .applauncher .header { + margin: 16.2px; + margin-bottom: 0; } + .applauncher .header image, .applauncher .header entry { + all: unset; + border-radius: 9px; + color: #f8f8f2; + background-color: rgba(68, 71, 90, 0.6); + border: 1px solid #44475a; + padding: 4.5px; } + .applauncher .header image { + margin-right: 9px; + -gtk-icon-transform: scale(0.8); + font-size: 25.6px; } + .applauncher scrolledwindow { + padding: 16.2px; + min-width: 700px; + min-height: 450px; } + .applauncher scrolledwindow scrollbar, .applauncher scrolledwindow scrollbar * { + all: unset; } + .applauncher scrolledwindow scrollbar.vertical { + transition: 200ms; + background-color: rgba(23, 23, 23, 0.3); } + .applauncher scrolledwindow scrollbar.vertical:hover { + background-color: rgba(23, 23, 23, 0.7); } + .applauncher scrolledwindow scrollbar.vertical:hover slider { + background-color: rgba(238, 238, 238, 0.7); + min-width: .6em; } + .applauncher scrolledwindow scrollbar.vertical slider { + background-color: rgba(238, 238, 238, 0.5); + border-radius: 9px; + min-width: .4em; + min-height: 2em; + transition: 200ms; } + .applauncher .placeholder { + margin-top: 9px; + color: #f8f8f2; + font-size: 1.2em; } + .applauncher .app { + all: unset; + transition: 200ms; + padding: 9px; } + .applauncher .app:active { + background-color: rgba(189, 147, 249, 0.5); + border-radius: 9px; + box-shadow: inset 0 0 0 3px rgba(238, 238, 238, 0.03); } + .applauncher .app:active .title { + color: #f8f8f2; } + .applauncher .app:hover .title, .applauncher .app:focus .title { + color: rgba(189, 147, 249, 0.8); } + .applauncher .app:hover image, .applauncher .app:focus image { + -gtk-icon-shadow: 2px 2px rgba(189, 147, 249, 0.8); } + .applauncher .app label { + transition: 200ms; } + .applauncher .app label.title { + color: #f8f8f2; } + .applauncher .app label.description { + color: rgba(238, 238, 238, 0.7); } + .applauncher .app image { + transition: 200ms; + margin-right: 9px; } diff --git a/config/hypr/main.conf b/config/hypr/main.conf index 2f68facd..3d41bc31 100644 --- a/config/hypr/main.conf +++ b/config/hypr/main.conf @@ -24,7 +24,7 @@ exec-once = XDG_DATA_DIRS=$kora ags exec-once = gnome-keyring-daemon --start --components=secrets exec-once = squeekboard -exec-once = wofi --show drun +exec-once = bash -c "sleep 1; ags -t applauncher" exec-once = hyprpaper exec-once = wl-paste --watch cliphist store @@ -172,8 +172,8 @@ bind = $mainMod, C, killactive, bind = $mainMod, L, exec, $LOCK_PATH/lock.sh bind = $mainMod SHIFT, E, exec, ags run-js 'ags.App.openWindow("powermenu")' bindn =, Escape, exec, ags run-js 'ags.App.closeAll()' -bind = $mainMod SHIFT, SPACE, togglefloating, -bind = $mainMod, D, exec, wofi --show drun +bind = $mainMod SHIFT, SPACE, togglefloating, +bind = $mainMod, D, exec, ags -t applauncher bind = $mainMod, P, pseudo, # dwindle bind = $mainMod, J, togglesplit, # dwindle