slint_borderless_windows 0.1.1

Borderless window with a custom titlebar for Slint applications on Windows, with native DWM shadows, rounded corners, and resize hit-testing.
export global WindowControls {
    callback minimize();
    callback maximize();
    callback close();
    callback drag();
    callback double-click();

    in-out property <bool> maximized: false;
}

export component Titlebar inherits Rectangle {
    in property <string> title: "Application";
    in property <image> icon;
    

    height: 32px;
    background: transparent;

    HorizontalLayout {
        padding: 0px;
        spacing: 0px;

        // Icon + Title + Drag area
        drag-area := TouchArea {
            horizontal-stretch: 1;
            pointer-event(event) => {
                if (event.kind == PointerEventKind.down && event.button == PointerEventButton.left) {
                    WindowControls.drag();
                }
            }
            double-clicked => {
                WindowControls.double-click();
            }

            HorizontalLayout {
                padding-left: 12px;
                padding-right: 8px;
                spacing: 8px;
                alignment: start;

                if root.icon.width > 0 && root.icon.height > 0: Image {
                    source: root.icon;
                    width: 16px;
                    height: 16px;
                    vertical-alignment: center;
                }

                Text {
                    text: root.title;
                    font-size: 12px;
                    vertical-alignment: center;
                    overflow: elide;
                }
            }
        }

        // Window control buttons
        minimize-btn := Rectangle {
            width: 46px;
            background: minimize-touch.has-hover ? #80808030 : transparent;
            animate background { duration: 100ms; }

            minimize-touch := TouchArea {
                clicked => { WindowControls.minimize(); }
            }

            // Minimize icon (horizontal line)
            Rectangle {
                width: 10px;
                height: 1px;
                background: #cccccc;
                x: (parent.width - self.width) / 2;
                y: (parent.height - self.height) / 2;
            }
        }

        maximize-btn := Rectangle {
            width: 46px;
            background: maximize-touch.has-hover ? #80808030 : transparent;
            animate background { duration: 100ms; }

            maximize-touch := TouchArea {
                clicked => { WindowControls.maximize(); }
            }

            if !WindowControls.maximized: Rectangle {
                // Maximize icon (square outline)
                width: 10px;
                height: 10px;
                x: (parent.width - self.width) / 2;
                y: (parent.height - self.height) / 2;
                border-width: 1px;
                border-color: #cccccc;
                background: transparent;
            }

            if WindowControls.maximized: Rectangle {
                // Restore icon (two overlapping squares)
                width: 12px;
                height: 12px;
                x: (parent.width - self.width) / 2;
                y: (parent.height - self.height) / 2;

                // Back square (offset top-right)
                Rectangle {
                    x: 2px;
                    y: 0px;
                    width: 10px;
                    height: 10px;
                    border-width: 1px;
                    border-color: #cccccc;
                    background: transparent;
                }

                // Front square (offset bottom-left)
                Rectangle {
                    x: 0px;
                    y: 2px;
                    width: 10px;
                    height: 10px;
                    border-width: 1px;
                    border-color: #cccccc;
                    background: #1e1e1e;
                }
            }
        }

        close-btn := Rectangle {
            width: 46px;
            background: close-touch.has-hover ? #c42b1c : transparent;
            animate background { duration: 100ms; }

            close-touch := TouchArea {
                clicked => { WindowControls.close(); }
            }

            // Close icon (X shape)
            Path {
                width: 10px;
                height: 10px;
                x: (parent.width - self.width) / 2;
                y: (parent.height - self.height) / 2;
                stroke: close-touch.has-hover ? #ffffff : #cccccc;
                stroke-width: 1px;
                animate stroke { duration: 100ms; }

                MoveTo { x: 0; y: 0; }
                LineTo { x: 1; y: 1; }
                MoveTo { x: 1; y: 0; }
                LineTo { x: 0; y: 1; }
            }
        }
    }
}