Compare commits
33 Commits
| Author | SHA1 | Date |
|---|---|---|
|
|
aae3c5fe70 | |
|
|
946b0191cd | |
|
|
8b611fe33e | |
|
|
13c99a8cd5 | |
|
|
5db08cb0d4 | |
|
|
4a66dccba3 | |
|
|
25881e033f | |
|
|
b9690bdf29 | |
|
|
80923b610b | |
|
|
dba911d2ae | |
|
|
815508b9e9 | |
|
|
5a0f12c57e | |
|
|
b0bc59200b | |
|
|
664926cb6d | |
|
|
e98924eb0c | |
|
|
089c657bac | |
|
|
870400dce6 | |
|
|
a52669eead | |
|
|
ee630ed600 | |
|
|
7dbd96a961 | |
|
|
aa1030f143 | |
|
|
d5b91a50c9 | |
|
|
1821a17eb6 | |
|
|
4ea1a272bc | |
|
|
188d4e7168 | |
|
|
d108f7a644 | |
|
|
4c3916e2d1 | |
|
|
365c724252 | |
|
|
eb79e560dc | |
|
|
f20d3916fe | |
|
|
c913f49e31 | |
|
|
029b23d470 | |
|
|
d9365975c6 |
|
|
@ -1,11 +1,13 @@
|
|||
name: build
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches: [main, master]
|
||||
paths:
|
||||
- 'egui_client/**'
|
||||
- 'rusty_hub/**'
|
||||
- ".github/**"
|
||||
- "rusty_hub_egui/**"
|
||||
- "unity_hub_lib/**"
|
||||
pull_request:
|
||||
branches: [main, master]
|
||||
|
||||
|
|
@ -16,7 +18,11 @@ jobs:
|
|||
build-unix:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Install dep
|
||||
- name: Hack sources.list
|
||||
run: sudo sed -i 's|http://azure.archive.ubuntu.com/ubuntu/|http://mirror.arizona.edu/ubuntu/|g' /etc/apt/sources.list
|
||||
- name: Update res
|
||||
run: sudo apt-get update
|
||||
- name: Install dependencies
|
||||
run: sudo apt-get install -y mesa-common-dev libx11-dev libxrandr-dev libxi-dev xorg-dev libatk1.0-dev librust-gdk-sys-dev
|
||||
- run: rustup toolchain install stable --profile minimal
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
|
|
@ -44,6 +50,10 @@ jobs:
|
|||
# Determines if the cache should be saved even when the workflow has failed.
|
||||
# Default: "false"
|
||||
cache-on-failure: ""
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v4
|
||||
- name: Build
|
||||
run: cd egui_client && cargo build --verbose
|
||||
run: cd rusty_hub_egui && cargo build --verbose
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: upload executable
|
||||
path: rusty_hub_egui/target/release/rusty_hub_egui
|
||||
|
|
|
|||
|
|
@ -12,10 +12,10 @@ jobs:
|
|||
steps:
|
||||
- run: rustup toolchain install stable --profile minimal
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v4
|
||||
- name: Build
|
||||
run: cd egui_client && cargo build --release --verbose
|
||||
- uses: actions/upload-artifact@v3
|
||||
run: cd rusty_hub_egui && cargo build --release --verbose
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: build-exe
|
||||
path: egui_client/target/release/*exe
|
||||
path: rusty_hub_egui/target/release/*exe
|
||||
|
|
|
|||
|
|
@ -1,2 +1,5 @@
|
|||
/target
|
||||
/*/target
|
||||
unity_hub_lib/Cargo.lock
|
||||
Cargo.lock
|
||||
/*/.idea
|
||||
|
|
|
|||
|
|
@ -0,0 +1,8 @@
|
|||
# [0.2.0]
|
||||
- Update egui to 0.21 version
|
||||
- Initial macOS support
|
||||
- Light theme
|
||||
|
||||
# [0.1.0]
|
||||
|
||||
- Initial version
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
|
||||
|
||||
[workspace]
|
||||
resolver = "3"
|
||||
members = ["rusty_hub_egui", "unity_hub_lib"]
|
||||
|
||||
[profile.release]
|
||||
opt-level = 'z'
|
||||
panic = 'abort'
|
||||
lto = true
|
||||
|
||||
[profile.dev.package."*"]
|
||||
opt-level = 2
|
||||
13
README.md
13
README.md
|
|
@ -1,24 +1,29 @@
|
|||
# Rusty Hub [](https://github.com/Leinnan/rusty_hub/actions/workflows/rust.yml)
|
||||
|
||||
Very simple alternative for Unity Hub. Rust pet project.
|
||||
https://www.mevlyshkin.com/projects/rusty_unity_hub/
|
||||
|
||||
|
||||
Very simple alternative for Unity Hub. Rust pet project. For now it does work properly only on Windows, but support for other platforms is on the roadmap.
|
||||
|
||||

|
||||
|
||||
## Download
|
||||
|
||||
There is ready to download Windows version. Go to [Releases](https://github.com/Leinnan/rusty_hub/releases) and look for `windows-build.zip` in `Assets`.
|
||||
|
||||
## Building and using
|
||||
|
||||
[Cargo](https://doc.rust-lang.org/cargo/getting-started/installation.html) is required in order to build it.
|
||||
|
||||
Building is pretty simple, just copy repo, open `egui_client` subdirectory in `CLI` client and run these commands:
|
||||
Building is pretty simple, just copy repo, open `rusty_hub_egui` subdirectory in `CLI` client and run these commands:
|
||||
|
||||
```sh
|
||||
git clone git@github.com:Leinnan/rusty_hub.git
|
||||
cd rusty_hub/egui_client
|
||||
cd rusty_hub/rusty_hub_egui
|
||||
cargo build --release
|
||||
cargo run --release
|
||||
```
|
||||
|
||||
|
||||
## Thanks
|
||||
|
||||
Big thanks to https://github.com/unitycoder/UnityLauncherPro
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -1,27 +0,0 @@
|
|||
[package]
|
||||
name = "rusty_hub_egui"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
homepage = "https://github.com/Leinnan/rusty_hub"
|
||||
build = "build.rs"
|
||||
|
||||
[profile.release]
|
||||
opt-level = 'z'
|
||||
panic = 'abort'
|
||||
lto = true
|
||||
|
||||
[profile.dev.package."*"]
|
||||
opt-level = 2
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
confy = "^0.5.0"
|
||||
eframe = "0.19.0"
|
||||
egui_extras = "0.19.0"
|
||||
rusty_hub = { path="../rusty_hub" }
|
||||
image = { version = "0.24.0", default-features = false, features = ["png"] }
|
||||
rfd = "0.10.0"
|
||||
|
||||
[target.'cfg(windows)'.build-dependencies]
|
||||
winres = "0.1"
|
||||
|
|
@ -1,448 +0,0 @@
|
|||
use crate::{
|
||||
consts::HOMEPAGE,
|
||||
consts::{APP_NAME, VERSION, VERTICAL_SPACING},
|
||||
window_tab::WindowTab,
|
||||
};
|
||||
use eframe::{
|
||||
egui::{self, Layout, Ui},
|
||||
epaint::Color32,
|
||||
};
|
||||
use egui_extras::{Size, TableBuilder};
|
||||
use rfd::FileDialog;
|
||||
use rusty_hub::hub::Hub;
|
||||
|
||||
pub struct HubClient {
|
||||
hub: Hub,
|
||||
current_tab: WindowTab,
|
||||
}
|
||||
|
||||
impl HubClient {
|
||||
pub fn new(_cc: &eframe::CreationContext<'_>) -> Self {
|
||||
let hub_option = confy::load("lwa_unity_hub", "config");
|
||||
|
||||
let hub = if hub_option.is_ok() {
|
||||
hub_option.unwrap()
|
||||
} else {
|
||||
Hub::default()
|
||||
};
|
||||
let mut client = Self {
|
||||
hub,
|
||||
current_tab: WindowTab::Projects,
|
||||
};
|
||||
client.save_config(true);
|
||||
client
|
||||
}
|
||||
|
||||
fn save_config(&mut self, rebuild: bool) {
|
||||
if rebuild {
|
||||
self.hub.update_info();
|
||||
}
|
||||
let _ = confy::store("lwa_unity_hub", "config", &self.hub);
|
||||
}
|
||||
|
||||
pub fn draw_central_panel(&mut self, ctx: &egui::Context) {
|
||||
egui::CentralPanel::default().show(ctx, |ui| {
|
||||
egui::ScrollArea::vertical().show(ui, |ui| {
|
||||
match self.current_tab {
|
||||
WindowTab::Projects => self.draw_project(&ctx, ui),
|
||||
WindowTab::Editors => self.draw_editors(&ctx, ui),
|
||||
};
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
fn draw_editors(&mut self, _ctx: &egui::Context, ui: &mut Ui) {
|
||||
ui.label(egui::RichText::new("Editor search paths").heading());
|
||||
ui.add_space(VERTICAL_SPACING);
|
||||
let text_height = egui::TextStyle::Body.resolve(&ui.style()).size * 2.0;
|
||||
|
||||
ui.scope(|ui| {
|
||||
let table = TableBuilder::new(ui)
|
||||
.striped(false)
|
||||
.scroll(false)
|
||||
.cell_layout(egui::Layout::left_to_right(egui::Align::Center))
|
||||
.column(Size::initial(150.0).at_least(150.0))
|
||||
.column(Size::remainder().at_least(260.0))
|
||||
.resizable(false);
|
||||
|
||||
let paths = self.hub.config.unity_search_paths.clone();
|
||||
table.body(|body| {
|
||||
body.rows(text_height, paths.len(), |row_index, mut row| {
|
||||
row.col(|ui| {
|
||||
ui.vertical_centered_justified(|ui| {
|
||||
ui.add_space(VERTICAL_SPACING - 2.0);
|
||||
if ui.button("Remove").clicked() {
|
||||
self.hub.config.unity_search_paths.remove(row_index);
|
||||
self.save_config(true);
|
||||
return;
|
||||
}
|
||||
});
|
||||
});
|
||||
row.col(|ui| {
|
||||
ui.with_layout(
|
||||
Layout::top_down_justified(eframe::emath::Align::Max),
|
||||
|ui| {
|
||||
ui.add_space(VERTICAL_SPACING);
|
||||
ui.label(&paths[row_index]);
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
ui.add_space(VERTICAL_SPACING * 2.0);
|
||||
|
||||
ui.label(egui::RichText::new("Installed editor versions").heading());
|
||||
ui.add_space(VERTICAL_SPACING);
|
||||
|
||||
let table2 = TableBuilder::new(ui)
|
||||
.striped(true)
|
||||
.scroll(false)
|
||||
.cell_layout(egui::Layout::left_to_right(egui::Align::Center))
|
||||
.column(Size::initial(100.0).at_least(100.0).at_most(120.0))
|
||||
.column(Size::initial(150.0).at_least(150.0).at_most(400.0))
|
||||
.column(Size::remainder().at_least(260.0))
|
||||
.resizable(false);
|
||||
|
||||
table2.body(|body| {
|
||||
body.rows(
|
||||
text_height,
|
||||
self.hub.config.editors_configurations.len(),
|
||||
|row_index, mut row| {
|
||||
let editor = &self.hub.config.editors_configurations[row_index];
|
||||
row.col(|ui| {
|
||||
ui.vertical_centered_justified(|ui| {
|
||||
ui.add_space(VERTICAL_SPACING);
|
||||
ui.label(&editor.version);
|
||||
});
|
||||
});
|
||||
row.col(|ui| {
|
||||
ui.vertical_centered_justified(|ui| {
|
||||
ui.add_space(VERTICAL_SPACING);
|
||||
ui.label(egui::RichText::new(editor.platforms.join(",")).small());
|
||||
});
|
||||
});
|
||||
row.col(|ui| {
|
||||
ui.with_layout(
|
||||
Layout::top_down_justified(eframe::emath::Align::Max),
|
||||
|ui| {
|
||||
ui.add_space(VERTICAL_SPACING);
|
||||
ui.label(&editor.base_path);
|
||||
},
|
||||
);
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
fn draw_project(&mut self, _ctx: &egui::Context, ui: &mut Ui) {
|
||||
let text_height = egui::TextStyle::Body.resolve(ui.style()).size * 2.0;
|
||||
|
||||
let table = TableBuilder::new(ui)
|
||||
.striped(true)
|
||||
.cell_layout(egui::Layout::left_to_right(egui::Align::Center))
|
||||
.column(Size::initial(150.0).at_least(150.0))
|
||||
.column(Size::initial(90.0).at_least(40.0))
|
||||
.column(Size::initial(90.0).at_least(90.0))
|
||||
.column(Size::remainder().at_least(260.0))
|
||||
.resizable(false);
|
||||
|
||||
table
|
||||
.header(25.0, |mut header| {
|
||||
header.col(|ui| {
|
||||
ui.heading("Name");
|
||||
ui.add_space(VERTICAL_SPACING);
|
||||
});
|
||||
header.col(|ui| {
|
||||
ui.vertical_centered_justified(|ui| {
|
||||
ui.heading("Version");
|
||||
ui.add_space(VERTICAL_SPACING);
|
||||
});
|
||||
});
|
||||
header.col(|ui| {
|
||||
ui.vertical_centered_justified(|ui| {
|
||||
ui.heading("Branch");
|
||||
ui.add_space(VERTICAL_SPACING);
|
||||
});
|
||||
});
|
||||
header.col(|ui| {
|
||||
ui.with_layout(
|
||||
Layout::top_down_justified(eframe::emath::Align::Max),
|
||||
|ui| {
|
||||
ui.heading("Directory");
|
||||
ui.add_space(VERTICAL_SPACING);
|
||||
},
|
||||
);
|
||||
});
|
||||
})
|
||||
.body(|body| {
|
||||
body.rows(
|
||||
text_height,
|
||||
self.hub.projects.len(),
|
||||
|row_index, mut row| {
|
||||
let project = &self.hub.projects[row_index];
|
||||
let editor_for_project_exists =
|
||||
self.hub.editor_for_project(project).is_some();
|
||||
row.col(|ui| {
|
||||
ui.vertical_centered_justified(|ui| {
|
||||
ui.add_space(VERTICAL_SPACING - 2.0);
|
||||
if ui
|
||||
.add_enabled(
|
||||
editor_for_project_exists,
|
||||
egui::Button::new(format!("{}", &project.title)),
|
||||
)
|
||||
.on_disabled_hover_text(format!(
|
||||
"Select different Unity version"
|
||||
))
|
||||
.clicked()
|
||||
{
|
||||
self.hub.run_project_nr(row_index);
|
||||
}
|
||||
ui.add_space(VERTICAL_SPACING);
|
||||
});
|
||||
});
|
||||
row.col(|ui| {
|
||||
ui.with_layout(
|
||||
Layout::top_down_justified(eframe::emath::Align::Center),
|
||||
|ui| {
|
||||
ui.add_space(VERTICAL_SPACING);
|
||||
let mut text = egui::RichText::new(&project.version);
|
||||
if !editor_for_project_exists {
|
||||
text = text.color(Color32::RED);
|
||||
}
|
||||
let version_response =
|
||||
ui.add(egui::Label::new(text).sense(egui::Sense::click()));
|
||||
version_response.context_menu(|ui| {
|
||||
for editor in &self.hub.config.editors_configurations {
|
||||
let mut text = egui::RichText::new(format!(
|
||||
"Open in {}",
|
||||
&editor.version
|
||||
));
|
||||
if editor.version.contains(&project.version) {
|
||||
text = text.strong().color(Color32::GREEN);
|
||||
}
|
||||
if ui.button(text).clicked() {
|
||||
Hub::run_project(&editor, &project);
|
||||
ui.close_menu();
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
row.col(|ui| {
|
||||
ui.with_layout(
|
||||
Layout::top_down_justified(eframe::emath::Align::Max),
|
||||
|ui| {
|
||||
if project.branch.len() < 1 {
|
||||
return;
|
||||
}
|
||||
ui.add_space(VERTICAL_SPACING);
|
||||
const MAX_BRANCH_LEN: usize = 15;
|
||||
let is_long = project.branch.len() > MAX_BRANCH_LEN;
|
||||
let short = if !is_long {
|
||||
project.branch.clone()
|
||||
} else {
|
||||
let mut result =
|
||||
String::from(&project.branch[0..MAX_BRANCH_LEN]);
|
||||
result.push_str("...");
|
||||
result
|
||||
};
|
||||
|
||||
let label = ui.label(egui::RichText::new(short).small());
|
||||
if is_long {
|
||||
label.on_hover_text(&project.branch);
|
||||
}
|
||||
},
|
||||
);
|
||||
});
|
||||
row.col(|ui| {
|
||||
ui.with_layout(
|
||||
Layout::top_down_justified(eframe::emath::Align::Max),
|
||||
|ui| {
|
||||
ui.add_space(VERTICAL_SPACING);
|
||||
let path_response = ui.add(
|
||||
egui::Label::new(&project.path).sense(egui::Sense::click()),
|
||||
);
|
||||
path_response.context_menu(|ui| {
|
||||
if ui.button("Open directory").clicked() {
|
||||
use std::process::Command;
|
||||
Command::new("explorer")
|
||||
.arg(&project.path)
|
||||
.spawn()
|
||||
.unwrap();
|
||||
ui.close_menu();
|
||||
}
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
fn draw_editors_header(&mut self, _ctx: &egui::Context, ui: &mut Ui) {
|
||||
let text_height = egui::TextStyle::Body.resolve(ui.style()).size * 2.0;
|
||||
let table = build_header_table(ui);
|
||||
table.body(|body| {
|
||||
body.rows(text_height, 1, |_, mut row| {
|
||||
row.col(|ui| {
|
||||
add_header(ui);
|
||||
});
|
||||
row.col(|ui| {
|
||||
ui.vertical_centered_justified(|ui| {
|
||||
ui.add_space(VERTICAL_SPACING);
|
||||
if ui
|
||||
.button("Add new path")
|
||||
.on_hover_text("Add new editor search path")
|
||||
.clicked()
|
||||
{
|
||||
let directory = FileDialog::new().pick_folder();
|
||||
if let Some(dir) = directory {
|
||||
self.hub
|
||||
.config
|
||||
.unity_search_paths
|
||||
.push(dir.into_os_string().into_string().unwrap());
|
||||
self.save_config(true);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
row.col(|_ui| {});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
fn draw_project_header(&mut self, _ctx: &egui::Context, ui: &mut Ui) {
|
||||
let text_height = egui::TextStyle::Body.resolve(ui.style()).size * 2.0;
|
||||
let table = build_header_table(ui);
|
||||
table.body(|body| {
|
||||
body.rows(text_height, 1, |_, mut row| {
|
||||
row.col(|ui| {
|
||||
add_header(ui);
|
||||
});
|
||||
row.col(|ui| {
|
||||
ui.vertical_centered_justified(|ui| {
|
||||
ui.add_space(VERTICAL_SPACING);
|
||||
if ui
|
||||
.button("Scan")
|
||||
.on_hover_text("Scan selected folder for projects")
|
||||
.clicked()
|
||||
{
|
||||
let directory = FileDialog::new().pick_folder();
|
||||
|
||||
if let Some(dir) = directory {
|
||||
let amount = self.hub.search_for_projects_at_path(&dir);
|
||||
let mut message =
|
||||
rfd::MessageDialog::new().set_title("Search ended");
|
||||
|
||||
match amount {
|
||||
0 => {
|
||||
message = message
|
||||
.set_description("No new projects found.")
|
||||
.set_level(rfd::MessageLevel::Warning)
|
||||
}
|
||||
1 => message = message.set_description("Project founded!"),
|
||||
_ => {
|
||||
message = message.set_description(&format!(
|
||||
"Founded {} projects.",
|
||||
amount
|
||||
))
|
||||
}
|
||||
}
|
||||
message.show();
|
||||
self.save_config(true);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
row.col(|_ui| {});
|
||||
});
|
||||
});
|
||||
}
|
||||
fn draw_side_panel(&mut self, ui: &mut Ui) {
|
||||
ui.with_layout(
|
||||
Layout::top_down_justified(eframe::emath::Align::Min),
|
||||
|ui| {
|
||||
ui.add_space(VERTICAL_SPACING);
|
||||
let button = egui::Button::new(egui::RichText::new("📦 Projects").heading())
|
||||
.frame(&self.current_tab == &WindowTab::Projects);
|
||||
if ui
|
||||
.add_enabled(&self.current_tab != &WindowTab::Projects, button)
|
||||
.clicked()
|
||||
{
|
||||
self.current_tab = WindowTab::Projects;
|
||||
}
|
||||
ui.add_space(VERTICAL_SPACING);
|
||||
if ui
|
||||
.add_enabled(
|
||||
&self.current_tab != &WindowTab::Editors,
|
||||
egui::Button::new(egui::RichText::new("🛠 Editors").heading())
|
||||
.frame(&self.current_tab == &WindowTab::Editors),
|
||||
)
|
||||
.clicked()
|
||||
{
|
||||
self.current_tab = WindowTab::Editors;
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn build_header_table(ui: &mut Ui) -> TableBuilder {
|
||||
let table = TableBuilder::new(ui)
|
||||
.striped(false)
|
||||
.scroll(false)
|
||||
.cell_layout(egui::Layout::left_to_right(egui::Align::Center))
|
||||
.column(Size::remainder().at_least(150.0))
|
||||
.column(Size::initial(100.0).at_most(100.0))
|
||||
.column(Size::initial(5.0).at_most(5.0))
|
||||
.resizable(false);
|
||||
table
|
||||
}
|
||||
|
||||
fn add_header(ui: &mut Ui) {
|
||||
ui.with_layout(
|
||||
Layout::top_down_justified(eframe::emath::Align::Min),
|
||||
|ui| {
|
||||
ui.add_space(5.0);
|
||||
let text = egui::RichText::new(APP_NAME).heading().strong();
|
||||
ui.add(egui::Label::new(text));
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
impl eframe::App for HubClient {
|
||||
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
|
||||
egui::TopBottomPanel::top("topPanel")
|
||||
.frame(egui::Frame::canvas(&ctx.style()))
|
||||
.show(ctx, |ui| {
|
||||
ui.horizontal(|ui| {
|
||||
ui.add_space(14.0);
|
||||
match self.current_tab {
|
||||
WindowTab::Projects => self.draw_project_header(&ctx, ui),
|
||||
WindowTab::Editors => self.draw_editors_header(&ctx, ui),
|
||||
};
|
||||
ui.add_space(14.0);
|
||||
});
|
||||
});
|
||||
|
||||
egui::SidePanel::left("dsadsa")
|
||||
.resizable(false)
|
||||
.frame(egui::Frame::canvas(&ctx.style()))
|
||||
.show(ctx, |ui| {
|
||||
self.draw_side_panel(ui);
|
||||
});
|
||||
self.draw_central_panel(&ctx);
|
||||
egui::TopBottomPanel::bottom("bottomPanel").show(ctx, |ui| {
|
||||
ui.with_layout(Layout::right_to_left(eframe::emath::Align::Center), |ui| {
|
||||
egui::widgets::global_dark_light_mode_switch(ui);
|
||||
ui.hyperlink_to(
|
||||
format!("{} v {}", egui::special_emojis::GITHUB, VERSION),
|
||||
HOMEPAGE,
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -1,38 +0,0 @@
|
|||
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release
|
||||
extern crate confy;
|
||||
use consts::{APP_NAME, VERSION};
|
||||
use eframe::{egui, IconData, NativeOptions};
|
||||
use std::io::Cursor;
|
||||
|
||||
mod consts;
|
||||
mod hub_client;
|
||||
mod window_tab;
|
||||
|
||||
fn main() {
|
||||
let img = image::io::Reader::new(Cursor::new(include_bytes!("../static/hub.png")))
|
||||
.with_guessed_format()
|
||||
.unwrap()
|
||||
.decode()
|
||||
.unwrap();
|
||||
let icon = IconData {
|
||||
width: 32,
|
||||
height: 32,
|
||||
rgba: img.into_rgba8().into_raw(),
|
||||
};
|
||||
let options = NativeOptions {
|
||||
always_on_top: false,
|
||||
maximized: false,
|
||||
decorated: true,
|
||||
fullscreen: false,
|
||||
drag_and_drop_support: false,
|
||||
initial_window_size: Some(egui::vec2(850.0, 400.0)),
|
||||
min_window_size: Some(egui::vec2(850.0, 400.0)),
|
||||
icon_data: Some(icon),
|
||||
..NativeOptions::default()
|
||||
};
|
||||
eframe::run_native(
|
||||
&format!("{} v {}", APP_NAME, VERSION),
|
||||
options,
|
||||
Box::new(|cc| Box::new(crate::hub_client::HubClient::new(cc))),
|
||||
);
|
||||
}
|
||||
|
|
@ -1,491 +0,0 @@
|
|||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "android_system_properties"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||
|
||||
[[package]]
|
||||
name = "block-buffer"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bumpalo"
|
||||
version = "3.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c1ad822118d20d2c234f427000d5acc36eabe1e29a348c89b63dd60b13f28e5d"
|
||||
|
||||
[[package]]
|
||||
name = "byteorder"
|
||||
version = "1.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "chrono"
|
||||
version = "0.4.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bfd4d1b31faaa3a89d7934dbded3111da0d2ef28e3ebccdb4f0179f5929d1ef1"
|
||||
dependencies = [
|
||||
"iana-time-zone",
|
||||
"js-sys",
|
||||
"num-integer",
|
||||
"num-traits",
|
||||
"time",
|
||||
"wasm-bindgen",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "core-foundation-sys"
|
||||
version = "0.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc"
|
||||
|
||||
[[package]]
|
||||
name = "cpufeatures"
|
||||
version = "0.2.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "digest"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "exe"
|
||||
version = "0.5.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2327a5b94310f33654812ff74ec7cd226a89eb430c46d4e1308c7e895da12f19"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"byteorder",
|
||||
"chrono",
|
||||
"hex",
|
||||
"md-5",
|
||||
"num-traits",
|
||||
"pkbuffer",
|
||||
"sha-1",
|
||||
"sha2",
|
||||
"widestring",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "generic-array"
|
||||
version = "0.14.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9"
|
||||
dependencies = [
|
||||
"typenum",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hex"
|
||||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
|
||||
|
||||
[[package]]
|
||||
name = "iana-time-zone"
|
||||
version = "0.1.50"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fd911b35d940d2bd0bea0f9100068e5b97b51a1cbe13d13382f132e0365257a0"
|
||||
dependencies = [
|
||||
"android_system_properties",
|
||||
"core-foundation-sys",
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.60"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "49409df3e3bf0856b916e2ceaca09ee28e6871cf7d9ce97a692cacfdb2a25a47"
|
||||
dependencies = [
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.133"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c0f80d65747a3e43d1596c7c5492d95d5edddaabd45a7fcdb02b95f644164966"
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "md-5"
|
||||
version = "0.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7b5a279bb9607f9f53c22d496eade00d138d1bdcccd07d74650387cf94942a15"
|
||||
dependencies = [
|
||||
"block-buffer",
|
||||
"digest",
|
||||
"opaque-debug",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-integer"
|
||||
version = "0.1.45"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e82dad04139b71a90c080c8463fe0dc7902db5192d939bd0950f074d014339e1"
|
||||
|
||||
[[package]]
|
||||
name = "opaque-debug"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
|
||||
|
||||
[[package]]
|
||||
name = "pkbuffer"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1c63758cdd196779b1d782c6c5f568cbd6b412eb3c51eab711f08e83cd57edef"
|
||||
dependencies = [
|
||||
"pkbuffer_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pkbuffer_derive"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7c86ad26b9715c9c1664b79f6e5c44baf38fb87a5133bdd7ec90baff7b71d155"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.43"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0a2ca2c61bc9f3d74d2886294ab7b9853abd9c1ad903a3ac7815c58989bb7bab"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "registry"
|
||||
version = "1.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "83e4b158bf49b0d000013487636c92268de4cfd26cdbb629f020a612749f12c4"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"log",
|
||||
"thiserror",
|
||||
"utfx",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rusty_hub"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"exe",
|
||||
"registry",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "same-file"
|
||||
version = "1.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
|
||||
dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.145"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "728eb6351430bccb993660dfffc5a72f91ccc1295abaa8ce19b27ebe4f75568b"
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.145"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "81fa1584d3d1bcacd84c277a0dfe21f5b0f6accf4a23d04d4c6d61f1af522b4c"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sha-1"
|
||||
version = "0.9.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "99cd6713db3cf16b6c84e06321e049a9b9f699826e16096d23bbcc44d15d51a6"
|
||||
dependencies = [
|
||||
"block-buffer",
|
||||
"cfg-if",
|
||||
"cpufeatures",
|
||||
"digest",
|
||||
"opaque-debug",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sha2"
|
||||
version = "0.9.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800"
|
||||
dependencies = [
|
||||
"block-buffer",
|
||||
"cfg-if",
|
||||
"cpufeatures",
|
||||
"digest",
|
||||
"opaque-debug",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.100"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "52205623b1b0f064a4e71182c3b18ae902267282930c6d5462c91b859668426e"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.35"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c53f98874615aea268107765aa1ed8f6116782501d18e53d08b471733bea6c85"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.35"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8b463991b4eab2d801e724172285ec4195c650e8ec79b149e6c2a8e6dd3f783"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "time"
|
||||
version = "0.1.44"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"wasi",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typenum"
|
||||
version = "1.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dcc811dc4066ac62f84f11307873c4850cb653bfa9b1719cee2bd2204a4bc5dd"
|
||||
|
||||
[[package]]
|
||||
name = "utfx"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "133bf74f01486773317ddfcde8e2e20d2933cc3b68ab797e5d718bef996a81de"
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
version = "0.9.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
|
||||
|
||||
[[package]]
|
||||
name = "walkdir"
|
||||
version = "2.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56"
|
||||
dependencies = [
|
||||
"same-file",
|
||||
"winapi",
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.10.0+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen"
|
||||
version = "0.2.83"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"wasm-bindgen-macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-backend"
|
||||
version = "0.2.83"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4c8ffb332579b0557b52d268b91feab8df3615f265d5270fec2a8c95b17c1142"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"log",
|
||||
"once_cell",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro"
|
||||
version = "0.2.83"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"wasm-bindgen-macro-support",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro-support"
|
||||
version = "0.2.83"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"wasm-bindgen-backend",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-shared"
|
||||
version = "0.2.83"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f"
|
||||
|
||||
[[package]]
|
||||
name = "widestring"
|
||||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c168940144dd21fd8046987c16a46a33d5fc84eec29ef9dcddc2ac9e31526b7c"
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
|
||||
dependencies = [
|
||||
"winapi-i686-pc-windows-gnu",
|
||||
"winapi-x86_64-pc-windows-gnu",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-i686-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||
|
||||
[[package]]
|
||||
name = "winapi-util"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
|
||||
dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-x86_64-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
[package]
|
||||
name = "rusty_hub"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[profile.release]
|
||||
opt-level = 'z'
|
||||
panic = 'abort'
|
||||
lto = true
|
||||
|
||||
[profile.dev.package."*"]
|
||||
opt-level = 2
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
serde = "^1.0"
|
||||
serde_derive = "^1.0"
|
||||
walkdir = "^2.3.2"
|
||||
exe = "^0.5.4"
|
||||
registry = "1.2.2"
|
||||
|
|
@ -1,77 +0,0 @@
|
|||
use walkdir::{DirEntry, WalkDir};
|
||||
|
||||
use crate::unity_editor::UnityEditor;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct Configuration {
|
||||
pub unity_search_paths: Vec<String>,
|
||||
pub editors_configurations: Vec<UnityEditor>,
|
||||
}
|
||||
|
||||
impl Configuration {
|
||||
pub fn rebuild(&mut self) {
|
||||
self.editors_configurations = Vec::new();
|
||||
let paths = self.get_unity_paths();
|
||||
for path in &paths {
|
||||
let editor = UnityEditor::new(&path);
|
||||
if editor.is_none() {
|
||||
continue;
|
||||
}
|
||||
let editor = editor.unwrap();
|
||||
if !self.editors_configurations.contains(&editor) {
|
||||
self.editors_configurations.push(editor);
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn get_unity_paths(&self) -> Vec<String> {
|
||||
let mut paths = Vec::new();
|
||||
|
||||
for path in &self.unity_search_paths {
|
||||
paths.extend(Configuration::search_for_editor(path.as_str()));
|
||||
}
|
||||
|
||||
paths
|
||||
}
|
||||
|
||||
fn is_unity_dir(entry: &DirEntry) -> bool {
|
||||
let uninstall_exists = entry.path().clone().join("Uninstall.exe").exists();
|
||||
let unity_exe_exists = entry.path().clone().join("Unity.exe").exists();
|
||||
|
||||
uninstall_exists && unity_exe_exists
|
||||
}
|
||||
|
||||
fn search_for_editor(path: &str) -> Vec<String> {
|
||||
let path_exists = std::fs::metadata(path).is_ok();
|
||||
if !path_exists {
|
||||
return Vec::new();
|
||||
}
|
||||
let mut result_paths: Vec<String> = Vec::new();
|
||||
|
||||
for entry in WalkDir::new(path)
|
||||
.max_depth(5)
|
||||
.into_iter()
|
||||
.filter_entry(|_| true)
|
||||
{
|
||||
if entry.is_ok() {
|
||||
let entry_unwraped = entry.unwrap();
|
||||
let success = Configuration::is_unity_dir(&entry_unwraped);
|
||||
if success {
|
||||
result_paths.push(entry_unwraped.path().to_string_lossy().into());
|
||||
}
|
||||
}
|
||||
}
|
||||
result_paths
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Configuration {
|
||||
fn default() -> Self {
|
||||
let mut default = Self {
|
||||
unity_search_paths: vec!["C:\\Program Files\\Unity\\Hub".to_string()],
|
||||
editors_configurations: Vec::new(),
|
||||
};
|
||||
default.rebuild();
|
||||
|
||||
default
|
||||
}
|
||||
}
|
||||
|
|
@ -1,99 +0,0 @@
|
|||
use exe::pe::VecPE;
|
||||
use exe::VSVersionInfo;
|
||||
use std::borrow::Borrow;
|
||||
use std::collections::HashMap;
|
||||
use std::path::Path;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct UnityEditor {
|
||||
pub version: String,
|
||||
pub exe_path: String,
|
||||
pub base_path: String,
|
||||
pub platforms: Vec<String>,
|
||||
}
|
||||
|
||||
impl PartialEq for UnityEditor {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.exe_path == other.exe_path
|
||||
}
|
||||
}
|
||||
|
||||
impl UnityEditor {
|
||||
pub fn new(path: &str) -> Option<Self> {
|
||||
let base_path = Path::new(path);
|
||||
let exe_path = base_path.join("Unity.exe");
|
||||
if !std::fs::metadata(&exe_path).is_ok() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let image = VecPE::from_disk_file(base_path.join("Unity.exe")).unwrap();
|
||||
let vs_version_check = VSVersionInfo::parse(&image);
|
||||
if vs_version_check.is_err() {
|
||||
return None;
|
||||
}
|
||||
let mut version = None;
|
||||
if let Some(string_file_info) = vs_version_check.unwrap().string_file_info {
|
||||
let hashmap = string_file_info.children[0].string_map();
|
||||
if let Some(result_version) = hashmap.get("ProductVersion") {
|
||||
version = Some(result_version.clone());
|
||||
if let Some(short) = result_version.clone().split("_").take(1).next() {
|
||||
version = Some(short.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if version.is_none() {
|
||||
None
|
||||
} else {
|
||||
Some(Self {
|
||||
version: version.unwrap().clone(),
|
||||
exe_path: exe_path.into_os_string().into_string().unwrap(),
|
||||
base_path: String::from(path),
|
||||
platforms: UnityEditor::get_platforms(path),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn get_platforms(unity_folder: &str) -> Vec<String> {
|
||||
let platform_names = HashMap::from([
|
||||
("androidplayer", "Android"),
|
||||
("windowsstandalonesupport", "Windows"),
|
||||
("linuxstandalonesupport", "Linux"),
|
||||
("LinuxStandalone", "Linux"),
|
||||
("OSXStandalone", "OSX"),
|
||||
("webglsupport", "WebGL"),
|
||||
("metrosupport", "UWP"),
|
||||
("iossupport", "iOS"),
|
||||
]);
|
||||
|
||||
let mut platforms = Vec::new();
|
||||
let base_path = Path::new(unity_folder).join("Data").join("PlaybackEngines");
|
||||
|
||||
if !std::fs::metadata(&base_path).is_ok() {
|
||||
return platforms;
|
||||
}
|
||||
let dir = std::fs::read_dir(base_path);
|
||||
|
||||
if dir.is_err() {
|
||||
return platforms;
|
||||
}
|
||||
|
||||
for path in dir.unwrap() {
|
||||
if path.is_err() {
|
||||
continue;
|
||||
}
|
||||
let f = path.unwrap();
|
||||
if let Ok(result_string) = f.file_name().into_string() {
|
||||
if let Some(value) =
|
||||
platform_names.get(&result_string.clone().to_lowercase().borrow())
|
||||
{
|
||||
platforms.push(value.to_string());
|
||||
} else {
|
||||
platforms.push(result_string);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
platforms
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
[package]
|
||||
name = "rusty_hub_egui"
|
||||
version = "0.2.0"
|
||||
edition = "2021"
|
||||
homepage = "https://github.com/Leinnan/rusty_hub"
|
||||
build = "build.rs"
|
||||
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
confy = "0.6"
|
||||
eframe = { version = "0.32", default-features = false, features = [
|
||||
#"accesskit", # Make egui comptaible with screen readers. NOTE: adds a lot of dependencies.
|
||||
"default_fonts", # Embed the default egui fonts.
|
||||
"glow", # Use the glow rendering backend. Alternative: "wgpu".
|
||||
"persistence", # Enable restoring app state when restarting the app.
|
||||
"wayland", # To support Linux (and CI)
|
||||
] }
|
||||
egui = "0.32"
|
||||
egui_extras = "0.32"
|
||||
unity_hub_lib = { path="../unity_hub_lib" }
|
||||
rfd = "0.15"
|
||||
inline_tweak = "1"
|
||||
anyhow = "1"
|
||||
|
||||
[target.'cfg(windows)'.build-dependencies]
|
||||
winres = "0.1"
|
||||
|
|
@ -9,5 +9,4 @@ fn main() {
|
|||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
fn main() {
|
||||
}
|
||||
fn main() {}
|
||||
|
|
@ -2,3 +2,6 @@ pub const VERSION: &str = env!("CARGO_PKG_VERSION");
|
|||
pub const HOMEPAGE: &str = env!("CARGO_PKG_HOMEPAGE");
|
||||
pub const APP_NAME: &str = "Rusty Unity Hub";
|
||||
pub const VERTICAL_SPACING: f32 = 8.0;
|
||||
pub const TOP_BUTTON_WIDTH: f32 = 150.0;
|
||||
pub const TOP_SIDE_MARGIN: f32 = 10.0;
|
||||
pub const HEADER_HEIGHT: f32 = 45.0;
|
||||
|
|
@ -0,0 +1,449 @@
|
|||
use crate::{
|
||||
consts::HOMEPAGE,
|
||||
consts::{
|
||||
APP_NAME, HEADER_HEIGHT, TOP_BUTTON_WIDTH, TOP_SIDE_MARGIN, VERSION, VERTICAL_SPACING,
|
||||
},
|
||||
window_tab::WindowTab,
|
||||
};
|
||||
use eframe::{
|
||||
egui::{self, CursorIcon, Layout, Ui},
|
||||
epaint::{Color32, FontFamily, FontId},
|
||||
};
|
||||
use egui_extras::{Column, TableBuilder};
|
||||
use inline_tweak::*;
|
||||
use rfd::FileDialog;
|
||||
use unity_hub_lib::{consts::FILE_MANAGER, hub::Hub};
|
||||
|
||||
pub struct HubClient {
|
||||
hub: Hub,
|
||||
current_tab: WindowTab,
|
||||
}
|
||||
|
||||
fn setup_custom_fonts(ctx: &egui::Context) {
|
||||
// Start with the default fonts (we will be adding to them rather than replacing them).
|
||||
let mut fonts = egui::FontDefinitions::default();
|
||||
if let Ok((regular, semibold)) = get_fonts() {
|
||||
fonts.font_data.insert(
|
||||
"regular".to_owned(),
|
||||
egui::FontData::from_owned(regular).into(),
|
||||
);
|
||||
fonts.font_data.insert(
|
||||
"semibold".to_owned(),
|
||||
egui::FontData::from_owned(semibold).into(),
|
||||
);
|
||||
|
||||
// Put my font first (highest priority) for proportional text:
|
||||
fonts
|
||||
.families
|
||||
.entry(egui::FontFamily::Proportional)
|
||||
.or_default()
|
||||
.insert(0, "regular".to_owned());
|
||||
fonts
|
||||
.families
|
||||
.entry(egui::FontFamily::Name("semibold".into()))
|
||||
.or_default()
|
||||
.insert(0, "semibold".to_owned());
|
||||
|
||||
// Put my font as last fallback for monospace:
|
||||
fonts
|
||||
.families
|
||||
.entry(egui::FontFamily::Monospace)
|
||||
.or_default()
|
||||
.push("regular".to_owned());
|
||||
|
||||
// Tell egui to use these fonts:
|
||||
ctx.set_fonts(fonts);
|
||||
}
|
||||
|
||||
ctx.style_mut(|style| {
|
||||
for font_id in style.text_styles.values_mut() {
|
||||
font_id.size *= 1.4;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
fn get_fonts() -> anyhow::Result<(Vec<u8>, Vec<u8>)> {
|
||||
use std::fs;
|
||||
|
||||
let font_path = std::path::Path::new("/System/Library/Fonts");
|
||||
|
||||
let regular = fs::read(font_path.join("SFNSRounded.ttf"))?;
|
||||
let semibold = fs::read(font_path.join("SFCompact.ttf"))?;
|
||||
|
||||
Ok((regular, semibold))
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn get_fonts() -> anyhow::Result<(Vec<u8>, Vec<u8>)> {
|
||||
use std::fs;
|
||||
let app_data = std::env::var("APPDATA")?;
|
||||
let font_path = std::path::Path::new(&app_data);
|
||||
|
||||
let regular = fs::read(font_path.join("../Local/Microsoft/Windows/Fonts/aptos.ttf"))?;
|
||||
let semibold = fs::read(font_path.join("../Local/Microsoft/Windows/Fonts/aptos-semibold.ttf"))?;
|
||||
|
||||
Ok((regular, semibold))
|
||||
}
|
||||
|
||||
impl HubClient {
|
||||
pub fn new(cc: &eframe::CreationContext<'_>) -> Self {
|
||||
setup_custom_fonts(&cc.egui_ctx);
|
||||
let hub_option = confy::load("rusty_hub_egui", "config");
|
||||
|
||||
let hub = if hub_option.is_ok() {
|
||||
let mut h: Hub = hub_option.unwrap();
|
||||
h.update_data();
|
||||
h
|
||||
} else {
|
||||
Hub::default()
|
||||
};
|
||||
|
||||
Self {
|
||||
hub,
|
||||
current_tab: WindowTab::Projects,
|
||||
}
|
||||
}
|
||||
|
||||
fn save_config(&mut self, rebuild: bool) {
|
||||
if rebuild {
|
||||
self.hub.update_data();
|
||||
}
|
||||
let _ = confy::store("rusty_hub_egui", "config", &self.hub);
|
||||
}
|
||||
|
||||
pub fn draw_central_panel(&mut self, ctx: &egui::Context) {
|
||||
egui::CentralPanel::default().show(ctx, |ui| {
|
||||
egui::ScrollArea::vertical().show(ui, |ui| {
|
||||
match self.current_tab {
|
||||
WindowTab::Projects => self.draw_project(ctx, ui),
|
||||
WindowTab::Editors => self.draw_editors(ctx, ui),
|
||||
};
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
fn draw_editors(&mut self, _ctx: &egui::Context, ui: &mut Ui) {
|
||||
ui.label(egui::RichText::new("Editor search paths").heading());
|
||||
ui.add_space(VERTICAL_SPACING);
|
||||
let text_height = egui::TextStyle::Body.resolve(ui.style()).size * 2.0;
|
||||
|
||||
let paths = self.hub.config.unity_search_paths.clone();
|
||||
for (i, path) in paths.iter().enumerate() {
|
||||
ui.horizontal(|ui| {
|
||||
ui.label(path);
|
||||
let height = tweak!(30.0);
|
||||
let button_width = tweak!(100.0);
|
||||
ui.allocate_space(egui::vec2(
|
||||
ui.available_width() - button_width - TOP_SIDE_MARGIN,
|
||||
height,
|
||||
));
|
||||
if ui
|
||||
.add_sized([button_width, height], egui::Button::new("🚮 Remove"))
|
||||
.clicked()
|
||||
{
|
||||
self.hub.config.unity_search_paths.remove(i);
|
||||
self.save_config(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
ui.add_space(VERTICAL_SPACING * 2.0);
|
||||
|
||||
ui.label(egui::RichText::new("Installed editor versions").heading());
|
||||
ui.add_space(VERTICAL_SPACING);
|
||||
|
||||
let table2 = TableBuilder::new(ui)
|
||||
.striped(true)
|
||||
.vscroll(false)
|
||||
.cell_layout(egui::Layout::left_to_right(egui::Align::Center))
|
||||
.column(Column::initial(100.0).at_least(100.0).at_most(120.0))
|
||||
.column(Column::initial(150.0).at_least(150.0).at_most(400.0))
|
||||
.column(Column::remainder().at_least(260.0))
|
||||
.resizable(false);
|
||||
|
||||
table2.body(|body| {
|
||||
body.rows(
|
||||
text_height,
|
||||
self.hub.config.editors_configurations.len(),
|
||||
|mut row| {
|
||||
let row_index = row.index();
|
||||
let editor = &self.hub.config.editors_configurations[row_index];
|
||||
row.col(|ui| {
|
||||
ui.vertical_centered_justified(|ui| {
|
||||
ui.add_space(VERTICAL_SPACING);
|
||||
ui.label(&editor.version);
|
||||
});
|
||||
});
|
||||
row.col(|ui| {
|
||||
ui.vertical_centered_justified(|ui| {
|
||||
ui.add_space(VERTICAL_SPACING);
|
||||
ui.label(egui::RichText::new(editor.platforms.join(",")).small());
|
||||
});
|
||||
});
|
||||
row.col(|ui| {
|
||||
ui.with_layout(
|
||||
Layout::top_down_justified(eframe::emath::Align::Max),
|
||||
|ui| {
|
||||
ui.add_space(VERTICAL_SPACING);
|
||||
let version_response = ui.add(
|
||||
egui::Label::new(&editor.base_path).sense(egui::Sense::click()),
|
||||
);
|
||||
version_response.context_menu(|ui| {
|
||||
let text = egui::RichText::new("🗁 Open directory");
|
||||
if ui.button(text).clicked() {
|
||||
use std::process::Command;
|
||||
Command::new(FILE_MANAGER)
|
||||
.arg(&editor.base_path)
|
||||
.spawn()
|
||||
.unwrap();
|
||||
ui.close();
|
||||
}
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
fn draw_project(&mut self, _ctx: &egui::Context, ui: &mut Ui) {
|
||||
let text_height = egui::TextStyle::Body.resolve(ui.style()).size * tweak!(3.0);
|
||||
|
||||
let projects = self.hub.projects.clone();
|
||||
for (i, project) in projects.iter().enumerate() {
|
||||
let editor_for_project_exists = self.hub.editor_for_project(project).is_some();
|
||||
ui.horizontal(|ui| {
|
||||
let color = if i % 2 == 0 {
|
||||
Color32::from_rgba_premultiplied(0, 0, 0, 30)
|
||||
} else {
|
||||
egui::Color32::TRANSPARENT
|
||||
};
|
||||
egui::Frame::NONE.fill(color).show(ui, |ui| {
|
||||
ui.add_sized(
|
||||
[text_height, text_height],
|
||||
egui::Button::new("⚙").frame(false),
|
||||
)
|
||||
.context_menu(|ui| {
|
||||
ui.menu_button("Open in", |ui| {
|
||||
if !editor_for_project_exists {
|
||||
ui.add_enabled(
|
||||
false,
|
||||
egui::Button::new(
|
||||
egui::RichText::new(format!(
|
||||
"Missing: {}",
|
||||
&project.version
|
||||
))
|
||||
.strong(),
|
||||
),
|
||||
);
|
||||
}
|
||||
for editor in &self.hub.config.editors_configurations {
|
||||
let mut text = egui::RichText::new(editor.version.to_string());
|
||||
if editor.version.contains(&project.version) {
|
||||
text = text.strong().color(Color32::GREEN);
|
||||
}
|
||||
if ui.button(text).clicked() {
|
||||
Hub::run_project(editor, project);
|
||||
ui.close();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if ui.button("Open directory").clicked() {
|
||||
use std::process::Command;
|
||||
Command::new(FILE_MANAGER)
|
||||
.arg(&project.path)
|
||||
.spawn()
|
||||
.unwrap();
|
||||
ui.close();
|
||||
}
|
||||
});
|
||||
ui.label(egui::RichText::new(project.title.to_string()).heading())
|
||||
.on_hover_text(&project.path);
|
||||
|
||||
if !project.branch.is_empty() {
|
||||
ui.add_space(TOP_SIDE_MARGIN);
|
||||
const MAX_BRANCH_LEN: usize = 15;
|
||||
let is_long = project.branch.len() > MAX_BRANCH_LEN;
|
||||
let short = if !is_long {
|
||||
project.branch.clone()
|
||||
} else {
|
||||
let mut result = String::from(&project.branch[0..MAX_BRANCH_LEN]);
|
||||
result.push_str("...");
|
||||
result
|
||||
};
|
||||
|
||||
let label = ui.label(egui::RichText::new(short).small().weak());
|
||||
if is_long {
|
||||
label.on_hover_text(format!(" {}", &project.branch));
|
||||
}
|
||||
}
|
||||
let btn_width = tweak!(100.0);
|
||||
ui.allocate_space(egui::vec2(
|
||||
ui.available_width() - btn_width - TOP_SIDE_MARGIN,
|
||||
text_height,
|
||||
));
|
||||
let text = if editor_for_project_exists {
|
||||
egui::RichText::new("Open".to_string())
|
||||
} else {
|
||||
egui::RichText::new("Missing").weak()
|
||||
};
|
||||
|
||||
let button = egui::Button::new(text);
|
||||
let added_button = ui.add_sized([btn_width, text_height], button);
|
||||
|
||||
if !editor_for_project_exists {
|
||||
added_button.on_hover_text_at_pointer(format!(
|
||||
"Missing {} Unity",
|
||||
&project.version
|
||||
));
|
||||
} else if added_button.clicked() {
|
||||
self.hub.run_project_nr(i);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn draw_editors_header(&mut self, _ctx: &egui::Context, ui: &mut Ui) {
|
||||
add_header(ui);
|
||||
|
||||
let available_width =
|
||||
ui.available_width() - TOP_BUTTON_WIDTH - TOP_SIDE_MARGIN - TOP_SIDE_MARGIN;
|
||||
ui.allocate_space(egui::vec2(available_width, HEADER_HEIGHT));
|
||||
if ui
|
||||
.add_sized(
|
||||
[TOP_BUTTON_WIDTH, 30.0],
|
||||
egui::Button::new("🖴 Add new path"),
|
||||
)
|
||||
.on_hover_text("Add new editor search path")
|
||||
.clicked()
|
||||
{
|
||||
let directory = FileDialog::new().pick_folder();
|
||||
if let Some(dir) = directory {
|
||||
self.hub
|
||||
.config
|
||||
.unity_search_paths
|
||||
.push(dir.into_os_string().into_string().unwrap());
|
||||
self.save_config(true);
|
||||
}
|
||||
}
|
||||
ui.allocate_space(egui::vec2(TOP_SIDE_MARGIN, 10.0));
|
||||
}
|
||||
|
||||
fn draw_project_header(&mut self, _ctx: &egui::Context, ui: &mut Ui) {
|
||||
add_header(ui);
|
||||
|
||||
let available_width =
|
||||
ui.available_width() - TOP_BUTTON_WIDTH - TOP_SIDE_MARGIN - TOP_SIDE_MARGIN;
|
||||
ui.allocate_space(egui::vec2(available_width, HEADER_HEIGHT));
|
||||
if ui
|
||||
.add_sized(
|
||||
[TOP_BUTTON_WIDTH, 30.0],
|
||||
egui::Button::new("🔭 Scan for projects"),
|
||||
)
|
||||
.on_hover_text("Scan selected folder for projects")
|
||||
.clicked()
|
||||
{
|
||||
let directory = FileDialog::new().pick_folder();
|
||||
|
||||
if let Some(dir) = directory {
|
||||
let amount = self.hub.search_for_projects_at_path(&dir);
|
||||
let mut message = rfd::MessageDialog::new().set_title("Search ended");
|
||||
|
||||
match amount {
|
||||
0 => {
|
||||
message = message
|
||||
.set_description("No new projects found.")
|
||||
.set_level(rfd::MessageLevel::Warning)
|
||||
}
|
||||
1 => message = message.set_description("Project founded!"),
|
||||
_ => message = message.set_description(format!("Founded {} projects.", amount)),
|
||||
}
|
||||
message.show();
|
||||
self.save_config(true);
|
||||
}
|
||||
}
|
||||
ui.allocate_space(egui::vec2(TOP_SIDE_MARGIN, 10.0));
|
||||
}
|
||||
|
||||
fn tab_button(&self, ui: &mut Ui, tab: &WindowTab, text: &str) -> bool {
|
||||
let button_size = tweak!(36.0);
|
||||
let font_size = tweak!(16.0);
|
||||
let button_size = egui::vec2(ui.available_width(), button_size);
|
||||
|
||||
let rich_text = if &self.current_tab == tab {
|
||||
egui::RichText::new(text).strong()
|
||||
} else {
|
||||
egui::RichText::new(text).weak()
|
||||
}
|
||||
.font(FontId::new(font_size, FontFamily::Proportional));
|
||||
|
||||
let response = ui.add_sized(
|
||||
button_size,
|
||||
egui::Label::new(rich_text)
|
||||
.selectable(false)
|
||||
.sense(egui::Sense::click()),
|
||||
);
|
||||
if response.hovered() {
|
||||
ui.output_mut(|o| o.cursor_icon = CursorIcon::PointingHand);
|
||||
}
|
||||
|
||||
response.clicked()
|
||||
}
|
||||
|
||||
fn draw_side_panel(&mut self, ui: &mut Ui) {
|
||||
ui.with_layout(Layout::top_down(eframe::emath::Align::Min), |ui| {
|
||||
if self.tab_button(ui, &WindowTab::Projects, tweak!("📦 Projects")) {
|
||||
self.current_tab = WindowTab::Projects;
|
||||
}
|
||||
if self.tab_button(ui, &WindowTab::Editors, tweak!("🛠 Editors")) {
|
||||
self.current_tab = WindowTab::Editors;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn add_header(ui: &mut Ui) {
|
||||
let text = egui::RichText::new(APP_NAME)
|
||||
.font(FontId::new(26.0, FontFamily::Name("semibold".into())))
|
||||
.strong();
|
||||
ui.allocate_space(egui::vec2(TOP_SIDE_MARGIN, HEADER_HEIGHT));
|
||||
ui.add(egui::Label::new(text).selectable(false));
|
||||
}
|
||||
|
||||
impl eframe::App for HubClient {
|
||||
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
|
||||
egui::TopBottomPanel::top("topPanel")
|
||||
.frame(egui::Frame::canvas(&ctx.style()))
|
||||
.show(ctx, |ui| {
|
||||
ui.with_layout(
|
||||
egui::Layout::left_to_right(egui::Align::Center)
|
||||
.with_cross_align(eframe::emath::Align::Center),
|
||||
|ui| {
|
||||
match self.current_tab {
|
||||
WindowTab::Projects => self.draw_project_header(ctx, ui),
|
||||
WindowTab::Editors => self.draw_editors_header(ctx, ui),
|
||||
};
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
egui::SidePanel::left("dsadsa")
|
||||
.resizable(false)
|
||||
.frame(egui::Frame::canvas(&ctx.style()))
|
||||
.show(ctx, |ui| {
|
||||
self.draw_side_panel(ui);
|
||||
});
|
||||
egui::TopBottomPanel::bottom("bottomPanel").show(ctx, |ui| {
|
||||
ui.with_layout(Layout::right_to_left(eframe::emath::Align::Center), |ui| {
|
||||
egui::widgets::global_theme_preference_switch(ui);
|
||||
ui.hyperlink_to(
|
||||
format!("{} v {}", egui::special_emojis::GITHUB, VERSION),
|
||||
HOMEPAGE,
|
||||
);
|
||||
});
|
||||
});
|
||||
self.draw_central_panel(ctx);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release
|
||||
|
||||
use consts::{APP_NAME, VERSION};
|
||||
use eframe::egui;
|
||||
|
||||
mod consts;
|
||||
mod hub_client;
|
||||
mod window_tab;
|
||||
|
||||
fn main() -> eframe::Result<()> {
|
||||
let options = eframe::NativeOptions {
|
||||
viewport: egui::ViewportBuilder::default()
|
||||
.with_inner_size([850.0, 400.0])
|
||||
.with_min_inner_size([850.0, 400.0])
|
||||
.with_icon(
|
||||
// NOTE: Adding an icon is optional
|
||||
eframe::icon_data::from_png_bytes(&include_bytes!("../static/hub.png")[..])
|
||||
.expect("Failed to load icon"),
|
||||
),
|
||||
..Default::default()
|
||||
};
|
||||
eframe::run_native(
|
||||
&format!("{} v {}", APP_NAME, VERSION),
|
||||
options,
|
||||
Box::new(|cc| {
|
||||
egui_extras::install_image_loaders(&cc.egui_ctx);
|
||||
Ok(Box::new(crate::hub_client::HubClient::new(cc)))
|
||||
}),
|
||||
)
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
Before Width: | Height: | Size: 190 KiB After Width: | Height: | Size: 190 KiB |
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
|
|
@ -0,0 +1,11 @@
|
|||
[package]
|
||||
name = "unity_hub_lib"
|
||||
version = "0.2.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
walkdir = "2"
|
||||
exe = "0.5"
|
||||
registry = "1.3"
|
||||
dpc-pariter = "0.5.1"
|
||||
|
|
@ -0,0 +1,79 @@
|
|||
use crate::{consts, unity_editor::UnityEditor};
|
||||
use dpc_pariter::IteratorExt;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashSet;
|
||||
use walkdir::{DirEntry, WalkDir};
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct Configuration {
|
||||
pub unity_search_paths: Vec<String>,
|
||||
pub editors_configurations: Vec<UnityEditor>,
|
||||
}
|
||||
|
||||
impl Configuration {
|
||||
pub fn rebuild(&mut self) {
|
||||
let paths = self.get_unity_paths();
|
||||
self.editors_configurations = paths
|
||||
.into_iter()
|
||||
.parallel_map(|path| UnityEditor::new(&path))
|
||||
.parallel_filter(|editor| editor.is_some())
|
||||
.parallel_map(|editor| editor.unwrap())
|
||||
.collect();
|
||||
}
|
||||
pub fn get_unity_paths(&self) -> Vec<String> {
|
||||
let mut paths = Vec::new();
|
||||
|
||||
for path in &self.unity_search_paths {
|
||||
paths.extend(Configuration::search_for_editor(path.as_str()));
|
||||
}
|
||||
|
||||
paths
|
||||
}
|
||||
|
||||
fn is_unity_dir(entry: &DirEntry) -> bool {
|
||||
#[cfg(windows)]
|
||||
let uninstall_exists = entry.path().join("Uninstall.exe").exists();
|
||||
#[cfg(unix)]
|
||||
let uninstall_exists = true; // just check that on windows only
|
||||
let unity_exe_exists = entry.path().join(consts::UNITY_EXE_NAME).exists();
|
||||
uninstall_exists && unity_exe_exists
|
||||
}
|
||||
|
||||
fn search_for_editor(path: &str) -> Vec<String> {
|
||||
let path_exists = std::fs::metadata(path).is_ok();
|
||||
if !path_exists {
|
||||
return Vec::new();
|
||||
}
|
||||
|
||||
let hashset: HashSet<String> = WalkDir::new(path)
|
||||
.max_depth(2)
|
||||
.into_iter()
|
||||
.parallel_filter(|entry| entry.is_ok())
|
||||
.parallel_map(|entry| entry.unwrap())
|
||||
.parallel_filter(|entry| Configuration::is_unity_dir(&entry))
|
||||
.parallel_map(|entry| entry.path().to_string_lossy().into())
|
||||
.collect();
|
||||
|
||||
Vec::from_iter(hashset)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Configuration {
|
||||
fn default() -> Self {
|
||||
let mut default = Self {
|
||||
#[cfg(windows)]
|
||||
unity_search_paths: vec!["C:\\Program Files\\Unity\\Hub\\Editor".to_string()],
|
||||
#[cfg(target_os = "macos")]
|
||||
unity_search_paths: vec![
|
||||
"/Applications/Unity/Hub/Editor".to_string(),
|
||||
"/Applications/Unity/".to_string(),
|
||||
],
|
||||
#[cfg(target_os = "linux")]
|
||||
unity_search_paths: vec!["~/Unity/Hub/Editor".to_string()],
|
||||
editors_configurations: Vec::new(),
|
||||
};
|
||||
default.rebuild();
|
||||
|
||||
default
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
#[cfg(windows)]
|
||||
pub const UNITY_EXE_NAME: &str = "Unity.exe";
|
||||
#[cfg(target_os = "macos")]
|
||||
pub const UNITY_EXE_NAME: &str = "Unity.app/Contents/MacOS/Unity";
|
||||
#[cfg(target_os = "linux")]
|
||||
pub const UNITY_EXE_NAME: &str = "Unity";
|
||||
|
||||
#[cfg(windows)]
|
||||
pub const SLASH: &str = "\\";
|
||||
#[cfg(unix)]
|
||||
pub const SLASH: &str = "/";
|
||||
#[cfg(windows)]
|
||||
pub const FILE_MANAGER: &str = "explorer";
|
||||
#[cfg(target_os = "macos")]
|
||||
pub const FILE_MANAGER: &str = "open";
|
||||
#[cfg(target_os = "linux")]
|
||||
pub const FILE_MANAGER: &str = "xdg-open";
|
||||
|
||||
#[cfg(windows)]
|
||||
pub const TEMPLATES_DIR: &str = "Data\\Resources\\PackageManager\\ProjectTemplates";
|
||||
#[cfg(target_os = "macos")]
|
||||
pub const TEMPLATES_DIR: &str = "Contents/Resources/PackageManager/ProjectTemplates";
|
||||
#[cfg(target_os = "linux")]
|
||||
pub const TEMPLATES_DIR: &str = "Data/Resources/PackageManager/ProjectTemplates";
|
||||
|
|
@ -1,8 +1,9 @@
|
|||
use std::{path::PathBuf, process::Command};
|
||||
|
||||
use walkdir::WalkDir;
|
||||
|
||||
use crate::{config::Configuration, unity_editor::UnityEditor, unity_project::UnityProject};
|
||||
use dpc_pariter::IteratorExt;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashSet;
|
||||
use std::{path::PathBuf, process::Command};
|
||||
use walkdir::WalkDir;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct Hub {
|
||||
|
|
@ -15,11 +16,28 @@ impl Hub {
|
|||
Self { config, projects }
|
||||
}
|
||||
|
||||
pub fn update_info(&mut self) {
|
||||
pub fn update_data(&mut self) {
|
||||
self.config.rebuild();
|
||||
for project in self.projects.iter_mut() {
|
||||
project.update_info();
|
||||
self.update_projects_info();
|
||||
}
|
||||
|
||||
pub fn update_projects_info(&mut self) {
|
||||
let mut registry = UnityProject::get_projects_from_registry()
|
||||
.into_iter()
|
||||
.filter(|p| !self.projects.contains(p))
|
||||
.collect();
|
||||
self.projects.append(&mut registry);
|
||||
self.projects = self
|
||||
.projects
|
||||
.iter()
|
||||
.cloned()
|
||||
.collect::<HashSet<UnityProject>>()
|
||||
.into_iter()
|
||||
.collect();
|
||||
self.projects.iter_mut().for_each(|project| {
|
||||
project.update_info();
|
||||
});
|
||||
self.projects.sort_by(|a, b| b.edit_time.cmp(&a.edit_time));
|
||||
}
|
||||
|
||||
pub fn run_project_nr(&self, nr: usize) {
|
||||
|
|
@ -52,30 +70,28 @@ impl Hub {
|
|||
|
||||
pub fn search_for_projects_at_path(&mut self, path: &PathBuf) -> usize {
|
||||
let path_exists = std::fs::metadata(path).is_ok();
|
||||
let mut result = 0;
|
||||
if !path_exists {
|
||||
return result;
|
||||
return 0;
|
||||
}
|
||||
for entry in WalkDir::new(path)
|
||||
let projects = self.projects.clone();
|
||||
let new_projects: Vec<UnityProject> = WalkDir::new(path)
|
||||
.max_depth(3)
|
||||
.into_iter()
|
||||
.filter_entry(|_| true)
|
||||
{
|
||||
let projects = self.projects.clone();
|
||||
if entry.is_err() {
|
||||
continue;
|
||||
}
|
||||
.parallel_filter(|entry| entry.is_ok())
|
||||
.parallel_map(|entry| {
|
||||
UnityProject::try_get_project_at_path(
|
||||
&entry.unwrap().path().as_os_str().to_str().unwrap(),
|
||||
)
|
||||
})
|
||||
.parallel_filter(|project| project.is_some())
|
||||
.parallel_map(|project| project.unwrap())
|
||||
.parallel_filter(move |p| !projects.contains(p))
|
||||
.collect();
|
||||
|
||||
let entry_unwraped = entry.unwrap();
|
||||
let path_string = entry_unwraped.path().as_os_str().to_str();
|
||||
if let Some(project) = UnityProject::try_get_project_at_path(&path_string.unwrap()) {
|
||||
if !projects.contains(&project) {
|
||||
self.projects.push(project);
|
||||
result = result + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
result
|
||||
let len = new_projects.len();
|
||||
self.projects.extend(new_projects);
|
||||
|
||||
len
|
||||
}
|
||||
}
|
||||
impl Default for Hub {
|
||||
|
|
@ -1,7 +1,6 @@
|
|||
#[macro_use]
|
||||
extern crate serde_derive;
|
||||
|
||||
pub mod config;
|
||||
pub mod consts;
|
||||
pub mod hub;
|
||||
pub mod project_template;
|
||||
pub mod unity_editor;
|
||||
pub mod unity_project;
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
use dpc_pariter::IteratorExt;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::path::Path;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, Eq, Hash)]
|
||||
pub struct ProjectTemplate {
|
||||
pub path: String,
|
||||
pub title: String,
|
||||
}
|
||||
|
||||
impl PartialEq for ProjectTemplate {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.path == other.path
|
||||
}
|
||||
}
|
||||
|
||||
impl ProjectTemplate {
|
||||
pub fn find_templates(path: &str) -> Vec<ProjectTemplate> {
|
||||
let dir = std::fs::read_dir(Path::new(&path).join(crate::consts::TEMPLATES_DIR));
|
||||
|
||||
if dir.is_err() {
|
||||
return Vec::new();
|
||||
}
|
||||
|
||||
dir.unwrap()
|
||||
.into_iter()
|
||||
.parallel_filter(|path| path.is_ok())
|
||||
.parallel_map(|path| path.unwrap())
|
||||
.parallel_filter(|path| path.file_name().into_string().unwrap().contains(".tgz"))
|
||||
.parallel_map(|path| Self {
|
||||
path: path.path().to_str().unwrap().to_string(),
|
||||
title: path.file_name().into_string().unwrap().replace(".tgz", ""),
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,122 @@
|
|||
use crate::consts;
|
||||
use crate::project_template::ProjectTemplate;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::borrow::Borrow;
|
||||
use std::collections::HashMap;
|
||||
use std::hash::Hash;
|
||||
use std::path::Path;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, Eq, Hash)]
|
||||
pub struct UnityEditor {
|
||||
pub version: String,
|
||||
pub exe_path: String,
|
||||
pub base_path: String,
|
||||
pub platforms: Vec<String>,
|
||||
pub templates: Vec<ProjectTemplate>,
|
||||
}
|
||||
|
||||
impl PartialEq for UnityEditor {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.exe_path == other.exe_path
|
||||
}
|
||||
}
|
||||
|
||||
impl UnityEditor {
|
||||
pub fn new(path: &str) -> Option<Self> {
|
||||
let base_path = Path::new(path);
|
||||
let exe_path = base_path.join(consts::UNITY_EXE_NAME);
|
||||
let meta = std::fs::metadata(&exe_path);
|
||||
if !meta.is_ok_and(|meta| meta.is_file()) {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut version: Option<String> = None;
|
||||
|
||||
#[cfg(windows)]
|
||||
{
|
||||
use exe::pe::VecPE;
|
||||
use exe::VSVersionInfo;
|
||||
let image = VecPE::from_disk_file(&exe_path).unwrap();
|
||||
let vs_version_check = VSVersionInfo::parse(&image);
|
||||
|
||||
if let Some(string_file_info) = vs_version_check.unwrap().string_file_info {
|
||||
let hashmap_result = string_file_info.children[0].string_map();
|
||||
|
||||
let hash_map = match hashmap_result {
|
||||
Ok(r) => r,
|
||||
Err(_) => HashMap::new(),
|
||||
};
|
||||
|
||||
if let Some(result_version) = hash_map.get("ProductVersion") {
|
||||
version = Some(result_version.clone());
|
||||
if let Some(short) = result_version.clone().split("_").take(1).next() {
|
||||
version = Some(short.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if version.is_none() {
|
||||
let folder = base_path
|
||||
.to_str()
|
||||
.expect("Fail")
|
||||
.split(consts::SLASH)
|
||||
.last()
|
||||
.unwrap();
|
||||
version = Some(folder.to_string());
|
||||
}
|
||||
|
||||
if version.is_none() {
|
||||
return None;
|
||||
}
|
||||
Some(Self {
|
||||
version: version.unwrap().clone(),
|
||||
exe_path: exe_path.into_os_string().into_string().unwrap(),
|
||||
base_path: String::from(path),
|
||||
platforms: UnityEditor::get_platforms(&path),
|
||||
templates: ProjectTemplate::find_templates(&path),
|
||||
})
|
||||
}
|
||||
|
||||
fn get_platforms(unity_folder: &str) -> Vec<String> {
|
||||
let platform_names = HashMap::from([
|
||||
("androidplayer", "Android"),
|
||||
("windowsstandalonesupport", "Windows"),
|
||||
("linuxstandalonesupport", "Linux"),
|
||||
("LinuxStandalone", "Linux"),
|
||||
("OSXStandalone", "OSX"),
|
||||
("webglsupport", "WebGL"),
|
||||
("metrosupport", "UWP"),
|
||||
("iossupport", "iOS"),
|
||||
]);
|
||||
|
||||
let mut platforms = Vec::new();
|
||||
let base_path = Path::new(unity_folder).join("Data").join("PlaybackEngines");
|
||||
|
||||
if std::fs::metadata(&base_path).is_err() {
|
||||
return platforms;
|
||||
}
|
||||
let dir = std::fs::read_dir(base_path);
|
||||
|
||||
if dir.is_err() {
|
||||
return platforms;
|
||||
}
|
||||
|
||||
for path in dir.unwrap() {
|
||||
if path.is_err() {
|
||||
continue;
|
||||
}
|
||||
let f = path.unwrap();
|
||||
if let Ok(result_string) = f.file_name().into_string() {
|
||||
if let Some(value) =
|
||||
platform_names.get(&result_string.clone().to_lowercase().borrow())
|
||||
{
|
||||
platforms.push(value.to_string());
|
||||
} else {
|
||||
platforms.push(result_string);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
platforms
|
||||
}
|
||||
}
|
||||
|
|
@ -1,12 +1,16 @@
|
|||
use std::{path::Path, str};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{ops::Sub, path::Path, str};
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
use crate::consts;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, Hash, Eq)]
|
||||
pub struct UnityProject {
|
||||
pub path: String,
|
||||
pub title: String,
|
||||
pub version: String,
|
||||
pub branch: String,
|
||||
pub is_valid: bool,
|
||||
pub edit_time: std::time::SystemTime,
|
||||
}
|
||||
|
||||
impl PartialEq for UnityProject {
|
||||
|
|
@ -31,7 +35,6 @@ impl UnityProject {
|
|||
Security::Read,
|
||||
)
|
||||
.unwrap();
|
||||
println!("{}", key.to_string());
|
||||
|
||||
for value in key.values() {
|
||||
if value.is_err() {
|
||||
|
|
@ -48,7 +51,6 @@ impl UnityProject {
|
|||
if let Some(result) = UnityProject::try_get_project_at_path(&project_path) {
|
||||
projects.push(result);
|
||||
}
|
||||
println!("\t{}: {}", unwraped_name, project_path);
|
||||
}
|
||||
}
|
||||
projects
|
||||
|
|
@ -61,33 +63,48 @@ impl UnityProject {
|
|||
std::fs::metadata(&one).is_ok() && std::fs::metadata(&two).is_ok()
|
||||
}
|
||||
|
||||
pub fn try_get_project_at_path(path: &str) -> Option<UnityProject> {
|
||||
let path = path.trim_matches(char::from(0)).replace("/", "\\");
|
||||
if !UnityProject::is_project_at_path(&path) {
|
||||
return None;
|
||||
}
|
||||
pub fn get_version_at_path(path: &str) -> Option<String> {
|
||||
let project_version_file = std::fs::read_to_string(
|
||||
Path::new(&path)
|
||||
.join("ProjectSettings")
|
||||
.join("ProjectVersion.txt"),
|
||||
);
|
||||
let project_version_file = project_version_file.unwrap();
|
||||
let mut iter = project_version_file.split_whitespace();
|
||||
if project_version_file.is_err() {
|
||||
return None;
|
||||
}
|
||||
let binding = project_version_file.unwrap();
|
||||
let mut iter = binding.split_whitespace();
|
||||
iter.next();
|
||||
let project_version = iter.next().unwrap().to_string();
|
||||
|
||||
Some(UnityProject {
|
||||
path: path.to_string(),
|
||||
title: path.split("\\").last().unwrap().to_string(),
|
||||
version: project_version,
|
||||
Some(project_version)
|
||||
}
|
||||
|
||||
pub fn try_get_project_at_path(path: &str) -> Option<UnityProject> {
|
||||
#[cfg(windows)]
|
||||
let path = path.trim_matches(char::from(0)).replace("/", "\\");
|
||||
#[cfg(not(windows))]
|
||||
let path = path.trim_matches(char::from(0)).to_string();
|
||||
if !UnityProject::is_project_at_path(&path) {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut project = UnityProject {
|
||||
path: path.clone(),
|
||||
title: path.split(consts::SLASH).last().unwrap().to_string(),
|
||||
branch: String::new(),
|
||||
version: String::new(),
|
||||
is_valid: true,
|
||||
})
|
||||
edit_time: std::time::SystemTime::now()
|
||||
.sub(std::time::Duration::new(60 * 60 * 24 * 365 * 30, 0)),
|
||||
};
|
||||
|
||||
project.update_info();
|
||||
|
||||
Some(project)
|
||||
}
|
||||
|
||||
pub fn update_info(&mut self) {
|
||||
const HEAD_PREFIX: &str = "ref: refs/heads/";
|
||||
|
||||
let is_project = UnityProject::is_project_at_path(&self.path);
|
||||
self.is_valid = is_project;
|
||||
|
||||
|
|
@ -96,18 +113,43 @@ impl UnityProject {
|
|||
}
|
||||
|
||||
let mut base_path = Path::new(&self.path);
|
||||
self.version = Self::get_version_at_path(&self.path).unwrap();
|
||||
|
||||
match self.try_read_from_path(base_path) {
|
||||
None => {
|
||||
while let Some(path) = base_path.parent() {
|
||||
base_path = path;
|
||||
let new_branch = self.try_read_from_path(base_path);
|
||||
if new_branch.is_some() {
|
||||
self.branch = new_branch.unwrap();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
Some(value) => {
|
||||
self.branch = value;
|
||||
}
|
||||
}
|
||||
|
||||
if let Ok(meta) = std::fs::metadata(&self.path) {
|
||||
if let Ok(data) = meta.modified() {
|
||||
self.edit_time = data;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn try_read_from_path(&self, path: &std::path::Path) -> Option<String> {
|
||||
const HEAD_PREFIX: &str = "ref: refs/heads/";
|
||||
|
||||
let head_path = Path::new(&path).join(".git").join("HEAD");
|
||||
if !head_path.exists() {
|
||||
continue;
|
||||
return None;
|
||||
}
|
||||
let head_content =
|
||||
std::fs::read_to_string(&head_path).expect("Could not read HEAD file");
|
||||
let head_content = std::fs::read_to_string(&head_path).expect("Could not read HEAD file");
|
||||
if head_content.contains(HEAD_PREFIX) {
|
||||
self.branch = head_content.replace(HEAD_PREFIX, "").trim().to_string();
|
||||
}
|
||||
Some(head_content.replace(HEAD_PREFIX, "").trim().to_string())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue