Customise window decorations, add openfolder action to menu, fix toolbar colours

This commit is contained in:
Michael Pivato
2024-06-08 16:30:05 +09:30
parent 8808b00f4d
commit ecdd4f7080
11 changed files with 253 additions and 167 deletions

View File

@@ -17,7 +17,7 @@ tauri-build = { version = "1.5.2", features = [] }
[dependencies] [dependencies]
serde_json = "1.0" serde_json = "1.0"
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
tauri = { version = "1.6.5", features = [ "dialog-all", "fs-all"] } tauri = { version = "1.6.5", features = [ "window-start-dragging", "dialog-all", "fs-all"] }
[features] [features]
# this feature is used for production builds or when `devPath` points to the filesystem and the built-in dev server is disabled. # this feature is used for production builds or when `devPath` points to the filesystem and the built-in dev server is disabled.

View File

@@ -1,8 +1,101 @@
// Prevents additional console window on Windows in release, DO NOT REMOVE!! // Prevents additional console window on Windows in release, DO NOT REMOVE!!
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
use std::any::Any;
use tauri::{AboutMetadata, CustomMenuItem, Menu, MenuEntry, MenuItem, Submenu};
#[derive(Clone, serde::Serialize)]
struct OpenFolderMessage {}
fn main() { fn main() {
let menu = build_menu("BufPiv");
tauri::Builder::default() tauri::Builder::default()
.menu(menu)
.on_menu_event(|event| match event.menu_item_id() {
"openfolder" => {
event
.window()
.emit("openfolder", OpenFolderMessage {})
.expect("Failed to emit openfolder event");
}
_ => {}
})
.run(tauri::generate_context!()) .run(tauri::generate_context!())
.expect("error while running tauri application"); .expect("error while running tauri application");
} }
fn build_menu(app_name: &str) -> Menu {
let mut menu = Menu::new();
#[cfg(target_os = "macos")]
{
menu = menu.add_submenu(Submenu::new(
app_name,
Menu::new()
.add_native_item(MenuItem::About(
app_name.to_string(),
AboutMetadata::default(),
))
.add_native_item(MenuItem::Separator)
.add_native_item(MenuItem::Services)
.add_native_item(MenuItem::Separator)
.add_native_item(MenuItem::Hide)
.add_native_item(MenuItem::HideOthers)
.add_native_item(MenuItem::ShowAll)
.add_native_item(MenuItem::Separator)
.add_native_item(MenuItem::Quit),
));
}
let open_folder =
CustomMenuItem::new("openfolder".to_string(), "Open Folder").accelerator("cmd+shift+O");
let file_submenu = Menu::new()
.add_item(open_folder)
.add_native_item(MenuItem::CloseWindow);
#[cfg(not(target_os = "macos"))]
{
file_submenu = file_submenu.add_native_item(MenuItem::Quit);
}
menu = menu.add_submenu(Submenu::new("File", file_submenu));
#[cfg(not(target_os = "linux"))]
let mut edit_menu = Menu::new();
#[cfg(target_os = "macos")]
{
edit_menu = edit_menu.add_native_item(MenuItem::Undo);
edit_menu = edit_menu.add_native_item(MenuItem::Redo);
edit_menu = edit_menu.add_native_item(MenuItem::Separator);
}
#[cfg(not(target_os = "linux"))]
{
edit_menu = edit_menu.add_native_item(MenuItem::Cut);
edit_menu = edit_menu.add_native_item(MenuItem::Copy);
edit_menu = edit_menu.add_native_item(MenuItem::Paste);
}
#[cfg(target_os = "macos")]
{
edit_menu = edit_menu.add_native_item(MenuItem::SelectAll);
}
#[cfg(not(target_os = "linux"))]
{
menu = menu.add_submenu(Submenu::new("Edit", edit_menu));
}
#[cfg(target_os = "macos")]
{
menu = menu.add_submenu(Submenu::new(
"View",
Menu::new().add_native_item(MenuItem::EnterFullScreen),
));
}
let mut window_menu = Menu::new();
window_menu = window_menu.add_native_item(MenuItem::Minimize);
#[cfg(target_os = "macos")]
{
window_menu = window_menu.add_native_item(MenuItem::Zoom);
window_menu = window_menu.add_native_item(MenuItem::Separator);
}
window_menu = window_menu.add_native_item(MenuItem::CloseWindow);
menu = menu.add_submenu(Submenu::new("Window", window_menu));
menu
}

View File

@@ -7,7 +7,7 @@
"distDir": "../dist" "distDir": "../dist"
}, },
"package": { "package": {
"productName": "Piv's Buf", "productName": "BufPiv",
"version": "0.0.0" "version": "0.0.0"
}, },
"tauri": { "tauri": {
@@ -18,16 +18,48 @@
}, },
"dialog": { "dialog": {
"all": true "all": true
},
"window": {
"all": false,
"center": false,
"close": false,
"create": false,
"hide": false,
"maximize": false,
"minimize": false,
"print": false,
"requestUserAttention": false,
"setAlwaysOnTop": false,
"setClosable": false,
"setContentProtected": false,
"setCursorGrab": false,
"setCursorIcon": false,
"setCursorPosition": false,
"setCursorVisible": false,
"setDecorations": false,
"setFocus": false,
"setFullscreen": false,
"setIcon": false,
"setIgnoreCursorEvents": false,
"setMaxSize": false,
"setMaximizable": false,
"setMinSize": false,
"setMinimizable": false,
"setPosition": false,
"setResizable": false,
"setSize": false,
"setSkipTaskbar": false,
"setTitle": false,
"show": false,
"startDragging": true,
"unmaximize": false,
"unminimize": false
} }
}, },
"bundle": { "bundle": {
"active": true, "active": true,
"category": "DeveloperTool", "category": "DeveloperTool",
"copyright": "", "copyright": "Michael Pivato 2024",
"deb": {
"depends": []
},
"externalBin": [],
"icon": [ "icon": [
"icons/32x32.png", "icons/32x32.png",
"icons/128x128.png", "icons/128x128.png",
@@ -36,7 +68,7 @@
"icons/icon.ico" "icons/icon.ico"
], ],
"identifier": "dev.michaelpivato", "identifier": "dev.michaelpivato",
"longDescription": "", "longDescription": "Create and edit protobuf and json files based on protobuf definitions.",
"macOS": { "macOS": {
"entitlements": null, "entitlements": null,
"exceptionDomain": "", "exceptionDomain": "",
@@ -64,8 +96,10 @@
"fullscreen": false, "fullscreen": false,
"height": 600, "height": 600,
"resizable": true, "resizable": true,
"title": "Piv's Buf", "title": "BufPiv",
"width": 800 "hiddenTitle": true,
"width": 800,
"titleBarStyle": "Overlay"
} }
] ]
} }

View File

@@ -1,11 +1,26 @@
<mat-toolbar><span>BufPiv</span></mat-toolbar> <mat-toolbar data-tauri-drag-region color="secondary"
><h1>BufPiv</h1></mat-toolbar
>
<mat-sidenav-container autosize> <mat-sidenav-container autosize>
@if(selectedDirectory()) {
<mat-sidenav mode="side" opened> <mat-sidenav mode="side" opened>
<app-file-tree [files]="files()"></app-file-tree> <app-file-tree [files]="files()"></app-file-tree>
</mat-sidenav> </mat-sidenav>
}
<mat-sidenav-content> <mat-sidenav-content>
@if (!selectedDirectory) { @if (!selectedDirectory()) {
<button mat-button (click)="selectDirectory()">Open Folder</button> <div
style="
display: flex;
justify-content: center;
align-items: center;
height: 100%;
"
>
<button mat-button (click)="selectDirectory()" style="margin: auto">
Open Folder
</button>
</div>
}@else { } }@else { }
</mat-sidenav-content> </mat-sidenav-content>
<!-- TODO: Proto selector (add/remove proto definitinos, select definition for current file) on right side --> <!-- TODO: Proto selector (add/remove proto definitinos, select definition for current file) on right side -->

View File

@@ -3,6 +3,12 @@
flex-direction: column; flex-direction: column;
height: 100%; height: 100%;
} }
mat-toolbar {
flex: 0 0 auto;
padding-top: 24px;
--mat-toolbar-standard-height: 70px;
}
mat-sidenav-container { mat-sidenav-container {
width: 100%; width: 100%;
height: 100%; height: 100%;

View File

@@ -1,5 +1,11 @@
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { Component, signal } from '@angular/core'; import {
ChangeDetectorRef,
Component,
OnDestroy,
OnInit,
signal,
} from '@angular/core';
import { MatButtonModule } from '@angular/material/button'; import { MatButtonModule } from '@angular/material/button';
import { MatSidenavModule } from '@angular/material/sidenav'; import { MatSidenavModule } from '@angular/material/sidenav';
import { RouterOutlet } from '@angular/router'; import { RouterOutlet } from '@angular/router';
@@ -12,6 +18,8 @@ import {
FileTreeComponent, FileTreeComponent,
} from './file-tree/file-tree.component'; } from './file-tree/file-tree.component';
import { MatIconModule } from '@angular/material/icon'; import { MatIconModule } from '@angular/material/icon';
import { listen, UnlistenFn } from '@tauri-apps/api/event';
import { OpenFolderMessage } from './messages/openfolder.message';
@Component({ @Component({
selector: 'app-root', selector: 'app-root',
@@ -30,9 +38,26 @@ import { MatIconModule } from '@angular/material/icon';
], ],
}) })
export class AppComponent { export class AppComponent {
protected selectedDirectory: string | null = null; protected selectedDirectory = signal<string | null>(null);
protected files = signal<FileOrFolder[]>([]); protected files = signal<FileOrFolder[]>([]);
private unlisten?: UnlistenFn;
async ngOnInit() {
this.unlisten = await listen(
'openfolder',
async (event: OpenFolderMessage) => {
await this.selectDirectory();
}
);
}
async ngOnDestroy() {
if (this.unlisten) {
this.unlisten();
}
}
async selectDirectory() { async selectDirectory() {
const selectedDirectory = await open({ const selectedDirectory = await open({
directory: true, directory: true,
@@ -40,22 +65,58 @@ export class AppComponent {
}); });
if (Array.isArray(selectedDirectory)) { if (Array.isArray(selectedDirectory)) {
message('Only a single folder can be selected at a time'); message('Only a single folder can be selected at a time');
return;
} else { } else {
this.selectedDirectory = selectedDirectory; this.selectedDirectory.set(selectedDirectory);
} }
if (this.selectedDirectory) { if (selectedDirectory) {
const entries = await readDir(this.selectedDirectory, { const entries = await readDir(selectedDirectory, {
recursive: true, recursive: true,
}); });
this.files.set(entries.map((entry) => this.mapEntry(entry))); const splitNumbers = /(\d)+|(\D)+/;
this.files.set(
entries
.sort(this.sortFiles(splitNumbers))
.map((entry) => this.mapEntry(entry, splitNumbers))
);
} }
} }
private mapEntry(entry: FileEntry): FileOrFolder { private mapEntry(entry: FileEntry, splitNumbers: RegExp): FileOrFolder {
return { return {
isDirectory: entry.children != null, isDirectory: entry.children != null,
name: entry.name || '', name: entry.name || '',
children: entry.children?.map((entry) => this.mapEntry(entry)), children: entry.children?.map((entry) =>
this.mapEntry(entry, splitNumbers)
),
}; };
} }
private sortFiles =
(splitNumbers: RegExp) => (a: FileEntry, b: FileEntry) => {
return a.name?.localeCompare(b.name ?? '') ?? 0;
// if (!a.name) {
// return -1;
// }
// if (!b.name) {
// return 1;
// }
// const aSplit = splitNumbers.exec(a.name);
// const bSplit = splitNumbers.exec(b.name);
// if (!aSplit) {
// return -1;
// }
// if (!bSplit) {
// return 1;
// }
// for (
// let i = 0;
// i < (aSplit.length > bSplit.length ? bSplit.length : aSplit.length);
// i++
// ) {}
// // TODO: Alternative is to iterate through characters in the strings to find numbers, if there isn't a number then return
// // locale compare?
// // TODO: finish this
// return 0;
};
} }

View File

@@ -40,7 +40,7 @@ interface FileNode {
<button mat-icon-button disabled></button> <button mat-icon-button disabled></button>
{{ node.file.name }} {{ node.file.name }}
</mat-tree-node> </mat-tree-node>
<mat-tree-node *matTreeNodeDef="let node; when: hasChild"> <mat-tree-node *matTreeNodeDef="let node; when: hasChild" matTreeNodeToggle>
<button <button
mat-icon-button mat-icon-button
matTreeNodePadding matTreeNodePadding
@@ -51,7 +51,7 @@ interface FileNode {
chevron_right } chevron_right }
</mat-icon> </mat-icon>
</button> </button>
{{ node.name }} {{ node.file.name }}
</mat-tree-node> </mat-tree-node>
</mat-tree>`, </mat-tree>`,
styleUrl: './file-tree.component.scss', styleUrl: './file-tree.component.scss',

View File

@@ -0,0 +1 @@
export interface OpenFolderMessage {}

View File

@@ -1,141 +0,0 @@
// This file was generated by running 'ng generate @angular/material:m3-theme'.
// Proceed with caution if making changes to this file.
@use 'sass:map';
@use '@angular/material' as mat;
// Note: Color palettes are generated from primary: #DF6B85, secondary: #AE868D, tertiary: #B48858, neutral: #988E8F
$_palettes: (
primary: (
0: #000000,
10: #3f0015,
20: #630828,
25: #721633,
30: #82233e,
35: #912f49,
40: #a13a54,
50: #c0536c,
60: #e06c86,
70: #ff86a0,
80: #ffb2bf,
90: #ffd9de,
95: #ffecee,
98: #fff8f7,
99: #fffbff,
100: #ffffff,
),
secondary: (
0: #000000,
10: #3f0017,
20: #5f112b,
25: #6d1d35,
30: #7c2940,
35: #8b344c,
40: #9a4058,
50: #b95870,
60: #d87189,
70: #f78aa3,
80: #ffb1c0,
90: #ffd9df,
95: #ffecee,
98: #fff8f7,
99: #fffbff,
100: #ffffff,
),
tertiary: (
0: #000000,
10: #2b1700,
20: #482900,
25: #573300,
30: #673d00,
35: #774700,
40: #885200,
50: #aa6800,
60: #c98121,
70: #e89b3a,
80: #ffb868,
90: #ffddbb,
95: #ffeedf,
98: #fff8f4,
99: #fffbff,
100: #ffffff,
),
neutral: (
0: #000000,
10: #3f0018,
20: #5f112c,
25: #6d1d37,
30: #7c2942,
35: #8b344e,
40: #9a4059,
50: #b85872,
60: #d7718b,
70: #f78aa5,
80: #ffb1c2,
90: #ffd9e0,
95: #ffecee,
98: #fff8f7,
99: #fffbff,
100: #ffffff,
),
neutral-variant: (
0: #000000,
10: #24191a,
20: #3a2d2f,
25: #46383a,
30: #524345,
35: #5e4f51,
40: #6a5a5c,
50: #847375,
60: #9f8c8e,
70: #baa6a9,
80: #d6c2c4,
90: #f3dddf,
95: #ffecee,
98: #fff8f7,
99: #fffbff,
100: #ffffff,
),
error: (
0: #000000,
10: #410002,
20: #690005,
25: #7e0007,
30: #93000a,
35: #a80710,
40: #ba1a1a,
50: #de3730,
60: #ff5449,
70: #ff897d,
80: #ffb4ab,
90: #ffdad6,
95: #ffedea,
98: #fff8f7,
99: #fffbff,
100: #ffffff,
),
);
$_rest: (
secondary: map.get($_palettes, secondary),
neutral: map.get($_palettes, neutral),
neutral-variant: map.get($_palettes, neutral-variant),
error: map.get($_palettes, error),
);
$_primary: map.merge(map.get($_palettes, primary), $_rest);
$_tertiary: map.merge(map.get($_palettes, tertiary), $_rest);
$light-theme: mat.define-theme((
color: (
theme-type: light,
primary: $_primary,
tertiary: $_tertiary,
),
));
$dark-theme: mat.define-theme((
color: (
theme-type: dark,
primary: $_primary,
tertiary: $_tertiary,
),
));

View File

@@ -1,10 +1,17 @@
@use '@angular/material' as mat; @use "@angular/material" as mat;
@use './m3-theme.scss'; @use "./theme.scss";
@include mat.core(); @include mat.core();
@mixin custom-colours($theme) {
.mat-toolbar {
background-color: mat.get-theme-color($theme, primary-container);
}
}
html { html {
@include mat.all-component-themes(m3-theme.$dark-theme); @include mat.all-component-themes(theme.$rose-theme);
@include custom-colours(theme.$rose-theme);
} }
html, html,

10
src/theme.scss Normal file
View File

@@ -0,0 +1,10 @@
@use "@angular/material" as mat;
$rose-theme: mat.define-theme(
(
color: (
theme-type: dark,
primary: mat.$rose-palette,
),
)
);