Skip to content
Snippets Groups Projects
Commit e86d192d authored by GyDi's avatar GyDi
Browse files

feat: custom window decorations

parent ea8f1c52
No related branches found
No related tags found
No related merge requests found
This diff is collapsed.
......@@ -4,7 +4,7 @@ version = "0.1.0"
description = "clash verge"
authors = ["zzzgydi"]
license = "GPL-3.0"
repository = ""
repository = "https://github.com/zzzgydi/clash-verge.git"
default-run = "app"
edition = "2021"
build = "build.rs"
......@@ -19,7 +19,9 @@ serde_json = "1.0"
serde_yaml = "0.8"
serde = { version = "1.0", features = ["derive"] }
# tauri = { version = "1.0.0-beta.8", features = ["api-all", "system-tray"] }
tauri = { git = "https://github.com/tauri-apps/tauri", rev = "5e0d59ec", features = ["api-all", "system-tray"] }
# tauri = { git = "https://github.com/tauri-apps/tauri", rev = "5e0d59ec", features = ["api-all", "system-tray"] }
tauri = { git = "https://github.com/tauri-apps/tauri", branch = "next", features = ["api-all", "system-tray"] }
tauri-plugin-shadows = { git = "https://github.com/tauri-apps/tauri-plugin-shadows", features = ["tauri-impl"] }
reqwest = { version = "0.11", features = ["json"] }
tokio = { version = "1", features = ["full"] }
......
......@@ -8,7 +8,7 @@ use crate::{
},
};
use serde_yaml::Mapping;
use tauri::State;
use tauri::{AppHandle, Manager, State};
/// get all profiles from `profiles.yaml`
/// do not acquire the lock of ProfileLock
......@@ -246,3 +246,25 @@ pub async fn patch_verge_config(
verge.config.save_file()
}
/// start dragging window
#[tauri::command]
pub fn win_drag(app_handle: AppHandle) {
let window = app_handle.get_window("main").unwrap();
window.start_dragging().unwrap();
}
/// hide the window
#[tauri::command]
pub fn win_hide(app_handle: AppHandle) {
let window = app_handle.get_window("main").unwrap();
window.hide().unwrap();
}
/// mini the window
#[tauri::command]
pub fn win_mini(app_handle: AppHandle) {
let window = app_handle.get_window("main").unwrap();
// todo: these methods still has bug on Windows
window.minimize().unwrap();
}
......@@ -62,14 +62,21 @@ fn main() -> std::io::Result<()> {
_ => {}
})
.invoke_handler(tauri::generate_handler![
// common
cmds::restart_sidecar,
cmds::set_sys_proxy,
cmds::get_sys_proxy,
cmds::get_cur_proxy,
cmds::win_drag,
cmds::win_hide,
cmds::win_mini,
// clash
cmds::get_clash_info,
cmds::patch_clash_config,
// verge
cmds::get_verge_config,
cmds::patch_verge_config,
// profile
cmds::import_profile,
cmds::update_profile,
cmds::delete_profile,
......@@ -86,10 +93,7 @@ fn main() -> std::io::Result<()> {
api.prevent_close();
app_handle.get_window(&label).unwrap().hide().unwrap();
}
tauri::Event::ExitRequested { api, .. } => {
api.prevent_exit();
}
tauri::Event::Exit => {
tauri::Event::ExitRequested { .. } => {
resolve::resolve_reset(app_handle);
api::process::kill_children();
}
......
use super::{init, server};
use crate::{core::ProfilesConfig, states};
use tauri::{App, AppHandle, Manager};
use tauri_plugin_shadows::Shadows;
/// handle something when start app
pub fn resolve_setup(app: &App) {
// set shadow when window decorations
let window = app.get_window("main").unwrap();
window.set_shadow(true);
// setup a simple http server for singleton
server::embed_server(&app.handle());
......@@ -34,7 +39,7 @@ pub fn resolve_setup(app: &App) {
/// reset system proxy
pub fn resolve_reset(app_handle: &AppHandle) {
let verge_state = app_handle.state::<states::VergeState>();
let mut verge_arc = verge_state.0.lock().unwrap();
let mut verge = verge_state.0.lock().unwrap();
verge_arc.reset_sysproxy();
verge.reset_sysproxy();
}
......@@ -5,7 +5,7 @@
},
"build": {
"distDir": "../dist",
"devPath": "http://localhost:3000/proxy",
"devPath": "http://localhost:3000/",
"beforeDevCommand": "yarn run web:dev",
"beforeBuildCommand": "yarn run web:build"
},
......@@ -62,7 +62,7 @@
"height": 600,
"resizable": true,
"fullscreen": false,
"decorations": true,
"decorations": false,
"transparent": false,
"minWidth": 600,
"minHeight": 520
......
.layout {
width: 100%;
height: 100vh;
display: flex;
overflow: hidden;
&__sidebar {
position: relative;
&__left {
flex: 1 0 25%;
height: 100vh;
display: flex;
height: 100%;
max-width: 225px;
min-width: 125px;
overflow: hidden auto;
padding: 8px 0;
flex-direction: column;
box-sizing: border-box;
user-select: none;
overflow: hidden;
.the-logo {
flex: 0 1 180px;
width: 100%;
max-width: 180px;
max-height: 180px;
margin: 0 auto;
padding: 0 8px;
text-align: center;
box-sizing: border-box;
}
.the-menu {
flex: 1 1 75%;
overflow-y: auto;
margin-bottom: 8px;
}
.the-traffic {
flex: 0 0 60px;
> div {
margin: 0 auto;
}
}
}
&__content {
&__right {
position: relative;
flex: 1 1 75%;
height: 100vh;
overflow: auto;
height: 100%;
display: flex;
flex-direction: column;
padding: 2px 0;
box-sizing: border-box;
scrollbar-gutter: "stable";
}
&__logo {
width: 100%;
height: auto;
max-width: 180px;
max-height: 180px;
margin: 0 auto;
padding: 8px 8px 0;
user-select: none;
text-align: center;
box-sizing: border-box;
}
&__traffic {
position: absolute;
left: 0;
right: 0;
bottom: 8px;
.the-bar {
flex: 0 0 30px;
width: 100%;
height: 30px;
padding: 0 16px;
display: flex;
align-items: center;
justify-content: flex-end;
box-sizing: border-box;
}
> div {
margin: 0 auto;
.the-content {
flex: 1 1 100%;
overflow: auto;
box-sizing: border-box;
scrollbar-gutter: stable;
}
}
}
......@@ -2,12 +2,18 @@ import { useEffect, useMemo } from "react";
import useSWR, { SWRConfig } from "swr";
import { Route, Routes } from "react-router-dom";
import { useRecoilState } from "recoil";
import { createTheme, List, Paper, ThemeProvider } from "@mui/material";
import {
createTheme,
IconButton,
List,
Paper,
ThemeProvider,
} from "@mui/material";
import { HorizontalRuleRounded, CloseRounded } from "@mui/icons-material";
import { atomPaletteMode } from "../states/setting";
import { getVergeConfig } from "../services/cmds";
import { getVergeConfig, windowDrag, windowHide } from "../services/cmds";
import LogoSvg from "../assets/image/logo.svg";
import LogPage from "./log";
import HomePage from "./home";
import ProfilePage from "./profile";
import ProxyPage from "./proxy";
import SettingPage from "./setting";
......@@ -18,23 +24,28 @@ import Traffic from "../components/traffic";
const routers = [
{
label: "Proxy",
link: "/proxy",
link: "/",
ele: ProxyPage,
},
{
label: "Profile",
link: "/profile",
ele: ProfilePage,
},
{
label: "Connections",
link: "/connections",
ele: ConnectionsPage,
},
{
label: "Log",
link: "/log",
ele: LogPage,
},
{
label: "Setting",
link: "/setting",
ele: SettingPage,
},
];
......@@ -47,39 +58,21 @@ const Layout = () => {
}, [vergeConfig?.theme_mode]);
const theme = useMemo(() => {
if (mode === "light") {
document.documentElement.style.background = "#f5f5f5";
document.documentElement.style.setProperty(
"--selection-color",
"#f5f5f5"
);
} else {
document.documentElement.style.background = "#000";
document.documentElement.style.setProperty(
"--selection-color",
"#d5d5d5"
);
}
// const background = mode === "light" ? "#f5f5f5" : "#000";
const selectColor = mode === "light" ? "#f5f5f5" : "#d5d5d5";
const rootEle = document.documentElement;
rootEle.style.background = "transparent";
rootEle.style.setProperty("--selection-color", selectColor);
return createTheme({
breakpoints: {
values: {
xs: 0,
sm: 650,
md: 900,
lg: 1200,
xl: 1536,
},
values: { xs: 0, sm: 650, md: 900, lg: 1200, xl: 1536 },
},
palette: {
mode,
primary: {
main: "#5b5c9d",
},
text: {
primary: "#637381",
secondary: "#909399",
},
primary: { main: "#5b5c9d" },
text: { primary: "#637381", secondary: "#909399" },
},
});
}, [mode]);
......@@ -88,12 +81,20 @@ const Layout = () => {
<SWRConfig value={{}}>
<ThemeProvider theme={theme}>
<Paper square elevation={0} className="layout">
<div className="layout__sidebar">
<div className="layout__logo">
<img src={LogoSvg} width="100%" alt="" />
<div className="layout__left">
<div className="the-logo">
<img
src={LogoSvg}
width="100%"
alt=""
onPointerDown={(e) => {
windowDrag();
e.preventDefault();
}}
/>
</div>
<List sx={{ userSelect: "none" }}>
<List className="the-menu">
{routers.map((router) => (
<LayoutItem key={router.label} to={router.link}>
{router.label}
......@@ -101,20 +102,35 @@ const Layout = () => {
))}
</List>
<div className="layout__traffic">
<div className="the-traffic">
<Traffic />
</div>
</div>
<div className="layout__content">
<Routes>
<Route path="/" element={<HomePage />} />
<Route path="/proxy" element={<ProxyPage />} />
<Route path="/profile" element={<ProfilePage />} />
<Route path="/log" element={<LogPage />} />
<Route path="/connections" element={<ConnectionsPage />} />
<Route path="/setting" element={<SettingPage />} />
</Routes>
<div className="layout__right">
<div
className="the-bar"
onPointerDown={(e) =>
e.target === e.currentTarget && windowDrag()
}
>
{/* todo: onClick = windowMini */}
<IconButton size="small" sx={{ mx: 1 }} onClick={windowHide}>
<HorizontalRuleRounded fontSize="inherit" />
</IconButton>
<IconButton size="small" onClick={windowHide}>
<CloseRounded fontSize="inherit" />
</IconButton>
</div>
<div className="the-content">
<Routes>
{routers.map(({ link, ele: Ele }) => (
<Route path={link} element={<Ele />} />
))}
</Routes>
</div>
</div>
</Paper>
</ThemeProvider>
......
import { Typography } from "@mui/material";
const HomePage = () => {
return (
<Typography variant="h1" textAlign="center" mt={10}>
Hello Clash!
</Typography>
);
};
export default HomePage;
......@@ -36,6 +36,18 @@ export async function restartSidecar() {
return invoke<void>("restart_sidecar");
}
export async function windowDrag() {
return invoke<void>("win_drag");
}
export async function windowHide() {
return invoke<void>("win_hide");
}
export async function windowMini() {
return invoke<void>("win_mini");
}
export async function getClashInfo() {
return invoke<CmdType.ClashInfo | null>("get_clash_info");
}
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment