// Copyright © SixtyFPS GmbH info@slint.dev // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 import { Palette, ScrollView, ListView, Switch } from "std-widgets.slint"; import { Api, LogMessage, LogMessageLevel } from "../api.slint"; import { RecentColorPicker } from "widgets/floating-brush-sections/palettes.slint"; import { SimpleColumn } from "layout-helpers.slint"; import { ConsoleStyles, EditorSizeSettings, Icons, } from "styling.slint"; import {WindowGlobal } from "../windowglobal.slint"; export component ConsolePanel inherits SimpleColumn { in-out property panel-expanded: false; property dragging-up: false; // Track if currently dragging upward property panel-height: ConsoleStyles.log-height; property drag-start-height; // Track height at start of drag property previous-height: 300px; // Track previous panel height for restoration property animate-panel: false; // Control animation state property drag-started: false; animate panel-height { duration: animate-panel ? 200ms : 0ms; easing: ease-out-quad; } changed panel-height => { if !dragging-up && panel-height <= (-ConsoleStyles.header-height) { panel-expanded = false; } } Rectangle { height: ConsoleStyles.header-height; background: ConsoleStyles.header-background; main-resize-handle := TouchArea { y: 0; height: 4px; mouse-cursor: ns-resize; moved => { if !panel-expanded && self.mouse-y < self.pressed-y { panel-expanded = true; dragging-up = true; drag-started = true; panel-height = Math.max(30px, -(self.mouse-y - self.pressed-y)); } else if panel-expanded { // Only update direction if we're actively moving if !drag-started { drag-started = true; dragging-up = self.mouse-y < self.pressed-y; } // Resizing panel during drag let max-height = WindowGlobal.window-height - 200px; let new-height = panel-height - (self.mouse-y - self.pressed-y); panel-height = Math.max((-ConsoleStyles.header-height), Math.min(max-height, new-height)); } } pointer-event(event) => { if event.kind == PointerEventKind.down { drag-started = false; animate-panel = false; // Disable animation during drag } if event.kind == PointerEventKind.up { animate-panel = true; } // Make close decision on release if event.kind == PointerEventKind.up && panel-expanded { if !dragging-up && panel-height <= 5px { // Store current height before closing (if it's a reasonable size) if panel-height > 50px { previous-height = panel-height; } panel-height = (-ConsoleStyles.header-height); } if dragging-up && panel-height <= 30px { panel-height = previous-height; // Restore previous height } // Also update previous-height when panel is at a good size if panel-height > 50px { previous-height = panel-height; } // Reset drag tracking dragging-up = false; drag-started = false; } } } Rectangle { y: 0; width: 100%; height: 2px; background: ConsoleStyles.divider-line; opacity: main-resize-handle.has-hover ? 1.0 : 0.3; } Rectangle { y: parent.height - self.height; width: 100%; height: 1px; background: ConsoleStyles.divider-line; opacity: 50%; } Rectangle { y: parent.height - self.height; width: 100%; height: 1px; background: ConsoleStyles.divider-line; visible: panel-expanded; } label := Text { x: 10px; horizontal-alignment: left; color: ConsoleStyles.text-color; font-family: "Inter"; font-size: 12px; text: @tr("Console"); } last-message := Text { x: label.width + label.x * 2; width: min(self.preferred-width, parent.width - self.x - (parent.width - info-layout.x) - 20px); overflow: elide; horizontal-alignment: left; color: ConsoleStyles.slint-blue; font-family: "Source Code Pro"; font-size: 11px; text: Api.log-output[Api.log-output.length - 1].message; visible: !panel-expanded; lm-ta := TouchArea { mouse-cursor: MouseCursor.pointer; clicked => { if panel-expanded { animate-panel = true; panel-height = (-ConsoleStyles.header-height); } else { panel-height = previous-height; panel-expanded = true; animate-panel = true; } } } } HorizontalLayout { alignment: end; info-layout := HorizontalLayout { spacing: 4px; visible: Api.log-output.length > 0; Image { width: 14px; source: Icons.info; colorize: ConsoleStyles.slint-blue; opacity: 0.9; TouchArea { mouse-cursor: MouseCursor.pointer; clicked => { panel-expanded = !panel-expanded; } } } Text { text: Api.log-output.length; vertical-alignment: center; color: ConsoleStyles.text-color; font-family: "Inter"; font-size: 12px; TouchArea { mouse-cursor: MouseCursor.pointer; clicked => { debug(parent.x); panel-expanded = !panel-expanded; } } } Rectangle { width: 10px; } } Rectangle { width: 36px; Rectangle { x: 0; width: 1px; height: 13px; background: ConsoleStyles.text-color; opacity: 0.3; } chevron-ta := TouchArea { mouse-cursor: MouseCursor.pointer; clicked => { if panel-expanded { animate-panel = true; panel-height = (-ConsoleStyles.header-height); } else { panel-height = previous-height; panel-expanded = true; animate-panel = true; } } } Image { x: parent.width - self.width - 10px; width: 16px; height: 16px; source: Icons.chevron-down; colorize: ConsoleStyles.text-color; opacity: chevron-ta.has-hover ? 1.0 : 0.5; rotation-angle: panel-expanded ? 0deg : 180deg; } } } } if panel-expanded: Rectangle { width: 100%; height: ConsoleStyles.header-height; background: ConsoleStyles.toolbar-background; Rectangle { x: 0; width: 36px; Image { width: 14px; source: Icons.clear; colorize: ConsoleStyles.text-color; opacity: cl-ta.has-hover ? 1.0 : 0.8; cl-ta := TouchArea { mouse-cursor: MouseCursor.pointer; clicked => { Api.clear-log-messages(); } } } Rectangle { x: parent.width - self.width; width: 1px; height: 13px; background: ConsoleStyles.text-color; opacity: 0.3; } } HorizontalLayout { alignment: end; Text { text: @tr("Auto clear logs"); font-family: "Inter"; font-size: 12px; color: ConsoleStyles.text-color; vertical-alignment: center; } Rectangle { width: 6px; } Switch { checked <=> Api.auto-clear-console; } Rectangle { width: 10px; } } Rectangle { y: parent.height - self.height; width: 100%; height: 1px; background: ConsoleStyles.divider-line; } } if panel-expanded: Rectangle { preferred-height: Math.min(panel-height, (WindowGlobal.window-height - 200px)); min-height: (-ConsoleStyles.header-height); background: ConsoleStyles.log-background; // Scroll to end workaround while this feature is missing from ScrollView property timer-clicked: 0; function scroll-to-bottom() { lv.viewport-y = -100000px; timer-clicked = 0; scroll-timer.running = true; } scroll-timer := Timer { running: true; interval: 1ms; triggered => { lv.viewport-y = -100000px; if timer-clicked < 1 { timer-clicked += 1; } else { self.running = false; } } } property scroll-to-end-trigger: Api.log-output.length; changed scroll-to-end-trigger => { scroll-to-bottom(); } lv := ListView { for lm[index] in Api.log-output: Rectangle { height: helper-message.height + 6px; // helper-message is used to calculate the height of the message // as TextInput gets into a binding loop if used alone. helper-message := Text { x: 10px; width: message.width; text: lm.message; font-family: message.font-family; font-size: message.font-size; horizontal-alignment: message.horizontal-alignment; font-weight: message.font-weight; wrap: message.wrap; visible: false; } message := TextInput { x: 10px; width: parent.width - 80px; read-only: true; text: lm.message; color: lm.level == LogMessageLevel.Error ? ConsoleStyles.slint-blue : ConsoleStyles.text-color; font-family: "Source Code Pro"; font-size: 11px; horizontal-alignment: left; vertical-alignment: center; font-weight: 200; wrap: TextWrap.word-wrap; } if !lm.file.is-empty: Rectangle { width: 40px; height: 14px; x: parent.width - self.width - EditorSizeSettings.standard-margin; file-link := Text { text: "\{lm.line}:\{lm.column}"; color: ConsoleStyles.text-color; font-family: "Source Code Pro"; font-size: 11px; } Rectangle { y: parent.height - self.height; width: file-link.width; height: 1px; background: ConsoleStyles.text-color; opacity: ta.has-hover ? 1.0 : 0.5; } ta := TouchArea { mouse-cursor: MouseCursor.pointer; clicked => { Api.show-document(lm.file, lm.line, lm.column); } } } Rectangle { y: parent.height - self.height; height: 1px; background: ConsoleStyles.text-color; opacity: 0.2; } } } } }