Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
111 changes: 108 additions & 3 deletions src/apps/components/shell/Shell/Taskbar/OpenedApps.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,122 @@
import { Daemon, Stack } from "$ts/env";
import { ProcessesHelper } from "$ts/helpers/processes";
import { isPopulatable } from "$ts/util/apps";
import { draggable } from "@neodrag/svelte";
import OpenedApp from "./OpenedApps/OpenedApp.svelte";
import type { IProcess } from "$interfaces/IProcess";
import { flip } from "svelte/animate";

const { process }: { process: IShellRuntime } = $props();
const { userPreferences } = process;
const { store } = Stack;

function shouldShow(openedProcess: IProcess) {
return (
ProcessesHelper.IsAnyGraphicalAppProcess(openedProcess) &&
!openedProcess._disposed &&
(isPopulatable(openedProcess.app.data) || openedProcess.overridePopulatable) &&
(!$userPreferences.shell.taskbar.openedAppsPerWorkspace ||
Daemon?.workspaces?.getDesktopIndexByUuid(openedProcess.app.desktop || "") === $userPreferences.workspaces.index)
);
}

let positions = $state<Map<number, { x: number; y: number }>>(new Map());
let tabs = $state<Map<number, IProcess>>(new Map());

// get any exisiting processes
// just in case
store()
.entries()
.forEach(([pid, process]) => {
if (shouldShow(process)) {
tabs.set(pid, process);
}
});

store.subscribe((v) => {
if (tabs.size === v.size) return;

// add new processes

v.entries().forEach(([pid, process]) => {
if (!tabs.keys().toArray().includes(pid) && shouldShow(process)) {
tabs.set(pid, process);
positions.set(pid, { x: 0, y: 0 });
}
});

// remove dead processes

tabs = new Map(
[...tabs.entries()].filter(([pid]) => {
return v.keys().toArray().includes(pid);
})
);

positions = new Map(
[...positions.entries()].filter(([pid]) => {
return v.keys().toArray().includes(pid);
})
);
});

function getPos(pid: number) {
if (!positions.has(pid)) {
positions.set(pid, { x: 0, y: 0 });
}
return positions.get(pid)!;
}
</script>

<div class="opened-apps">
{#each [...$store] as [pid, openedProcess] (pid)}
{#if ProcessesHelper.IsAnyGraphicalAppProcess(openedProcess) && !openedProcess._disposed && (isPopulatable(openedProcess.app.data) || openedProcess.overridePopulatable) && (!$userPreferences.shell.taskbar.openedAppsPerWorkspace || Daemon?.workspaces?.getDesktopIndexByUuid(openedProcess.app.desktop || "") === $userPreferences.workspaces.index)}
{#each [...tabs] as [pid, openedProcess] (pid)}
<div
class="drag-container"
animate:flip={{ duration: 500 }}
use:draggable={{
bounds: "parent",
axis: "x",
threshold: {
delay: 125,
},
position: getPos(pid),
onDragEnd(data) {
const iconButton = data.currentNode.firstElementChild as HTMLElement | null;
if (!iconButton) return;

const movementActuationDistance = 0.75;
const iconDragDist = data.offsetX / iconButton.offsetWidth;
const iconDragDistRounded = iconDragDist >= 0 ? Math.floor(iconDragDist) : Math.ceil(iconDragDist);
const iconGridDragDist =
Math.abs(iconDragDist) - Math.abs(iconDragDistRounded) >= movementActuationDistance
? iconDragDist < 0
? iconDragDistRounded - 1
: iconDragDistRounded + 1
: iconDragDistRounded;

const currentEntryIdx = tabs
.keys()
.toArray()
.findIndex((val) => {
return val === pid;
});

const currentEntry = tabs.entries().toArray().at(currentEntryIdx);
if (!currentEntry) return;

// begin repositioning

const entries = [...tabs];
entries.splice(currentEntryIdx, 1);
entries.splice(currentEntryIdx + iconGridDragDist, 0, currentEntry);
tabs = new Map(entries);

// force reset position so it's not visually offset
positions.set(pid, { x: 0, y: 0 });
},
}}
>
<OpenedApp {pid} openedProcess={openedProcess as any} {process} />
{/if}
</div>
{/each}
</div>
16 changes: 11 additions & 5 deletions src/css/apps/components/shell/taskbar/openedapps.css
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,12 @@ div.shell div.taskbar div.opened-apps::-webkit-scrollbar {
width: 1px;
}

#arcShell div.shell div.taskbar div.opened-apps button {
#arcShell div.shell div.taskbar div.opened-apps div.drag-container {
display: inline-flex;
}

#arcShell div.shell div.taskbar div.opened-apps button {
display: flex;
max-width: 180px;
align-items: center;
gap: 10px;
Expand Down Expand Up @@ -52,7 +56,7 @@ div.shell div.taskbar div.opened-apps::-webkit-scrollbar {
transform-origin: left;
}

#arcShell div.shell div.taskbar div.opened-apps button.iconic.closing + button {
#arcShell div.shell div.taskbar div.opened-apps button.iconic.closing+button {
margin-left: -40px;
transition:
margin-left 0.3s,
Expand All @@ -71,7 +75,7 @@ div.shell div.taskbar div.opened-apps::-webkit-scrollbar {
white-space: nowrap;
}

#arcShell div.shell div.taskbar div.opened-apps button + button {
#arcShell div.shell div.taskbar div.opened-apps div:has(button)+div:has(button) {
margin-left: 5px;
}

Expand Down Expand Up @@ -104,10 +108,12 @@ div.shell div.taskbar div.opened-apps button:hover img.backdrop {
#arcShell div.shell div.taskbar button.start-button.active {
background-color: var(--button-glass-active-bg);
}

#arcShell div.shell div.taskbar div.opened-apps button {
outline: var(--shell-component-outline) 1px solid !important;
outline-offset: -1px;
}

div.shell div.taskbar div.opened-apps button.active img.backdrop {
opacity: 0.5;
filter: brightness(1.3) blur(7px);
Expand All @@ -122,12 +128,12 @@ div.shell div.taskbar div.opened-apps button.active img.backdrop {
opacity: 0.7;
}

#arcShell > div.body div.taskbar button.opened-app.blinking {
#arcShell>div.body div.taskbar button.opened-app.blinking {
animation: blinking 0.7s infinite alternate;
}

@keyframes blinking {
to {
background-color: var(--clr-orange-fg);
}
}
}
Loading