Unlucky Launcher v0.0.1-alpha
CI / fmt (rustfmt --check) (push) Canceled after 0s
CI / clippy (ubuntu-latest) (push) Canceled after 0s
CI / clippy (windows-latest) (push) Canceled after 0s
CI / test (ubuntu-latest) (push) Canceled after 0s
CI / test (windows-latest) (push) Canceled after 0s
CI / build release (ubuntu-latest) (push) Canceled after 0s
CI / build release (windows-latest) (push) Canceled after 0s
Guards / core 零 UI 守门 (push) Canceled after 0s
Guards / 文件行数 ≤400 守门 (push) Canceled after 0s
Guards / 依赖许可与来源(cargo-deny) (push) Canceled after 0s
CI / fmt (rustfmt --check) (push) Canceled after 0s
CI / clippy (ubuntu-latest) (push) Canceled after 0s
CI / clippy (windows-latest) (push) Canceled after 0s
CI / test (ubuntu-latest) (push) Canceled after 0s
CI / test (windows-latest) (push) Canceled after 0s
CI / build release (ubuntu-latest) (push) Canceled after 0s
CI / build release (windows-latest) (push) Canceled after 0s
Guards / core 零 UI 守门 (push) Canceled after 0s
Guards / 文件行数 ≤400 守门 (push) Canceled after 0s
Guards / 依赖许可与来源(cargo-deny) (push) Canceled after 0s
Rust 原生单文件 Minecraft 启动器:CLI 完整可用(install/launch/accounts/java/diagnose/agent), MCP server 接入外部 Agent 工具,GUI 开发中。
This commit is contained in:
commit
bc41dfcf7d
517 files changed
+92844
No files matched your search
@@ -0,0 +1,354 @@
|
||||
//! 回调接线:把 `ui/main.slint` 暴露的每个回调翻译成「状态变更 + AppCommand + 同步」。
|
||||
//!
|
||||
//! 逐条列出而不做通用转发,是为了让「用户能做的每一件事」在这里一目了然;
|
||||
//! 新增交互时只改这一处 + `.slint` 的 callback 声明。
|
||||
|
||||
use std::rc::Weak;
|
||||
|
||||
use slint::ComponentHandle;
|
||||
|
||||
use crate::conv;
|
||||
use crate::dto::AppCommand;
|
||||
use crate::router::Route;
|
||||
use crate::toast::ToastKind;
|
||||
use crate::ui_bind::Inner;
|
||||
|
||||
/// 接全部回调;闭包持弱引用,避免 `MainWindow` 与闭包互相持有导致窗口永不释放。
|
||||
pub(crate) fn wire(weak: Weak<Inner>) {
|
||||
let Some(inner) = weak.upgrade() else { return };
|
||||
// 只在注册期间持有强引用;闭包内一律持弱引用(否则窗口与闭包互相持有、永不释放)。
|
||||
let ui = &inner.ui;
|
||||
|
||||
wire_window(ui, &weak);
|
||||
wire_navigation(ui, &weak);
|
||||
wire_instances(ui, &weak);
|
||||
wire_download(ui, &weak);
|
||||
wire_logs(ui, &weak);
|
||||
wire_settings(ui, &weak);
|
||||
wire_overlays(ui, &weak);
|
||||
}
|
||||
|
||||
fn wire_window(ui: &crate::MainWindow, weak: &Weak<Inner>) {
|
||||
{
|
||||
let weak = weak.clone();
|
||||
ui.on_toggle_maximize(move || {
|
||||
let Some(this) = weak.upgrade() else { return };
|
||||
let window = this.ui.window();
|
||||
window.set_maximized(!window.is_maximized());
|
||||
});
|
||||
}
|
||||
{
|
||||
let weak = weak.clone();
|
||||
ui.on_minimize(move || {
|
||||
if let Some(this) = weak.upgrade() {
|
||||
this.ui.window().set_minimized(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
{
|
||||
let weak = weak.clone();
|
||||
ui.on_request_close(move || {
|
||||
if let Some(this) = weak.upgrade() {
|
||||
let _ = this.ui.hide();
|
||||
}
|
||||
let _ = slint::quit_event_loop();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn wire_navigation(ui: &crate::MainWindow, weak: &Weak<Inner>) {
|
||||
{
|
||||
let weak = weak.clone();
|
||||
ui.on_navigate(move |route| {
|
||||
let Some(this) = weak.upgrade() else { return };
|
||||
if let Some(route) = conv::route_from(route) {
|
||||
this.state.borrow_mut().navigate(route);
|
||||
this.sync();
|
||||
}
|
||||
});
|
||||
}
|
||||
{
|
||||
let weak = weak.clone();
|
||||
ui.on_toggle_nav(move || {
|
||||
let Some(this) = weak.upgrade() else { return };
|
||||
this.state.borrow_mut().toggle_nav();
|
||||
this.sync();
|
||||
});
|
||||
}
|
||||
{
|
||||
let weak = weak.clone();
|
||||
ui.on_toggle_ai(move || {
|
||||
let Some(this) = weak.upgrade() else { return };
|
||||
this.state.borrow_mut().toggle_ai();
|
||||
this.sync();
|
||||
});
|
||||
}
|
||||
{
|
||||
let weak = weak.clone();
|
||||
ui.on_expand_ai(move || {
|
||||
let Some(this) = weak.upgrade() else { return };
|
||||
this.state.borrow_mut().navigate(Route::Agent);
|
||||
this.sync();
|
||||
});
|
||||
}
|
||||
{
|
||||
let weak = weak.clone();
|
||||
ui.on_dock_ai(move || {
|
||||
let Some(this) = weak.upgrade() else { return };
|
||||
this.state.borrow_mut().back();
|
||||
this.sync();
|
||||
});
|
||||
}
|
||||
{
|
||||
let weak = weak.clone();
|
||||
ui.on_toggle_yolo(move || {
|
||||
let Some(this) = weak.upgrade() else { return };
|
||||
let enabled = {
|
||||
let mut state = this.state.borrow_mut();
|
||||
state.ai.yolo = !state.ai.yolo;
|
||||
state.ai.yolo
|
||||
};
|
||||
this.dispatch(AppCommand::SetYolo(enabled));
|
||||
this.sync();
|
||||
});
|
||||
}
|
||||
{
|
||||
let weak = weak.clone();
|
||||
ui.on_ai_send(move |text| {
|
||||
let Some(this) = weak.upgrade() else { return };
|
||||
let text = text.to_string();
|
||||
if text.trim().is_empty() {
|
||||
return;
|
||||
}
|
||||
// 不做本地回显:user 消息由 host 的 MessageAppended 事件带回(契约 §3)
|
||||
this.state.borrow_mut().ai.draft.clear();
|
||||
this.ui.set_ai_draft("".into());
|
||||
this.ask_agent(&text);
|
||||
this.sync();
|
||||
});
|
||||
}
|
||||
{
|
||||
let weak = weak.clone();
|
||||
ui.on_ai_stop(move || {
|
||||
let Some(this) = weak.upgrade() else { return };
|
||||
this.state.borrow_mut().ai.finish_stream();
|
||||
this.dispatch(AppCommand::StopAgent);
|
||||
this.sync();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn wire_instances(ui: &crate::MainWindow, weak: &Weak<Inner>) {
|
||||
{
|
||||
let weak = weak.clone();
|
||||
ui.on_instance_launch(move |id| {
|
||||
let Some(this) = weak.upgrade() else { return };
|
||||
this.dispatch(AppCommand::LaunchInstance(id.to_string()));
|
||||
this.sync();
|
||||
});
|
||||
}
|
||||
{
|
||||
let weak = weak.clone();
|
||||
ui.on_instance_configure(move |id| {
|
||||
let Some(this) = weak.upgrade() else { return };
|
||||
// 齿轮键进实例详情(G4 占位页),实例级设置后续挂在该页 tab 上
|
||||
this.state
|
||||
.borrow_mut()
|
||||
.navigate(Route::Detail(id.to_string()));
|
||||
this.sync();
|
||||
});
|
||||
}
|
||||
{
|
||||
let weak = weak.clone();
|
||||
ui.on_instance_stop(move |id| {
|
||||
let Some(this) = weak.upgrade() else { return };
|
||||
this.dispatch(AppCommand::StopInstance(id.to_string()));
|
||||
this.sync();
|
||||
});
|
||||
}
|
||||
{
|
||||
let weak = weak.clone();
|
||||
ui.on_instance_open(move |id| {
|
||||
let Some(this) = weak.upgrade() else { return };
|
||||
this.state
|
||||
.borrow_mut()
|
||||
.navigate(Route::Detail(id.to_string()));
|
||||
this.sync();
|
||||
});
|
||||
}
|
||||
{
|
||||
let weak = weak.clone();
|
||||
ui.on_new_instance(move || {
|
||||
let Some(this) = weak.upgrade() else { return };
|
||||
this.state
|
||||
.borrow_mut()
|
||||
.toasts
|
||||
.push(ToastKind::Info, "新建实例向导(G5)在 P1 落地");
|
||||
this.sync();
|
||||
});
|
||||
}
|
||||
{
|
||||
let weak = weak.clone();
|
||||
ui.on_instance_view_changed(move |view| {
|
||||
let Some(this) = weak.upgrade() else { return };
|
||||
this.state.borrow_mut().prefs.instance_view = conv::instance_view_from(view);
|
||||
this.sync();
|
||||
});
|
||||
}
|
||||
{
|
||||
let weak = weak.clone();
|
||||
ui.on_tile_size_changed(move |size| {
|
||||
let Some(this) = weak.upgrade() else { return };
|
||||
this.state.borrow_mut().prefs.tile_size = conv::tile_size_from(size);
|
||||
this.sync();
|
||||
});
|
||||
}
|
||||
{
|
||||
let weak = weak.clone();
|
||||
ui.on_instance_query_changed(move |query| {
|
||||
let Some(this) = weak.upgrade() else { return };
|
||||
this.state.borrow_mut().instance_query = query.to_string();
|
||||
this.sync();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn wire_download(ui: &crate::MainWindow, weak: &Weak<Inner>) {
|
||||
{
|
||||
let weak = weak.clone();
|
||||
ui.on_download_category_changed(move |category| {
|
||||
let Some(this) = weak.upgrade() else { return };
|
||||
let category = conv::version_category(category);
|
||||
this.state.borrow_mut().prefs.download_category = category;
|
||||
this.dispatch(AppCommand::ListVersions(category));
|
||||
this.sync();
|
||||
});
|
||||
}
|
||||
{
|
||||
let weak = weak.clone();
|
||||
ui.on_download_install(move |id| {
|
||||
let Some(this) = weak.upgrade() else { return };
|
||||
this.dispatch(AppCommand::InstallVersion(id.to_string()));
|
||||
this.sync();
|
||||
});
|
||||
}
|
||||
{
|
||||
let weak = weak.clone();
|
||||
ui.on_installed_only_changed(move |only| {
|
||||
let Some(this) = weak.upgrade() else { return };
|
||||
this.state.borrow_mut().installed_only = only;
|
||||
this.sync();
|
||||
});
|
||||
}
|
||||
{
|
||||
let weak = weak.clone();
|
||||
ui.on_download_query_changed(move |query| {
|
||||
let Some(this) = weak.upgrade() else { return };
|
||||
this.state.borrow_mut().download_query = query.to_string();
|
||||
this.sync();
|
||||
});
|
||||
}
|
||||
{
|
||||
let weak = weak.clone();
|
||||
ui.on_ancient_help(move || {
|
||||
let Some(this) = weak.upgrade() else { return };
|
||||
this.state.borrow_mut().toasts.push(
|
||||
ToastKind::Info,
|
||||
"远古版(classic/alpha/beta)需旧版 Java 与旧启动参数;完整说明页 P1 接入",
|
||||
);
|
||||
this.sync();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn wire_logs(ui: &crate::MainWindow, weak: &Weak<Inner>) {
|
||||
{
|
||||
let weak = weak.clone();
|
||||
ui.on_log_select(move |id| {
|
||||
let Some(this) = weak.upgrade() else { return };
|
||||
this.state.borrow_mut().selected_session = Some(id.to_string());
|
||||
this.dispatch(AppCommand::OpenLogSession(id.to_string()));
|
||||
this.sync();
|
||||
});
|
||||
}
|
||||
{
|
||||
let weak = weak.clone();
|
||||
ui.on_log_ask_ai(move |id| {
|
||||
let Some(this) = weak.upgrade() else { return };
|
||||
let question = format!("帮我看下会话 {id} 为什么崩溃");
|
||||
this.state.borrow_mut().navigate(Route::Agent);
|
||||
this.ask_agent(&question);
|
||||
this.sync();
|
||||
});
|
||||
}
|
||||
{
|
||||
let weak = weak.clone();
|
||||
ui.on_log_apply_fix(move || {
|
||||
let Some(this) = weak.upgrade() else { return };
|
||||
this.state
|
||||
.borrow_mut()
|
||||
.toasts
|
||||
.push(ToastKind::Warn, "一键修复在 P1 接 diagnose 的 fix_action");
|
||||
this.sync();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn wire_settings(ui: &crate::MainWindow, weak: &Weak<Inner>) {
|
||||
// 主题三态 + 外观四件在 appearance_bind.rs(拆文件守 400 行)
|
||||
crate::appearance_bind::wire(ui, weak);
|
||||
{
|
||||
let weak = weak.clone();
|
||||
ui.on_yolo_changed(move |enabled| {
|
||||
let Some(this) = weak.upgrade() else { return };
|
||||
this.state.borrow_mut().ai.yolo = enabled;
|
||||
this.dispatch(AppCommand::SetYolo(enabled));
|
||||
this.sync();
|
||||
});
|
||||
}
|
||||
{
|
||||
let weak = weak.clone();
|
||||
ui.on_mcp_changed(move |_enabled| {
|
||||
let Some(this) = weak.upgrade() else { return };
|
||||
this.state
|
||||
.borrow_mut()
|
||||
.toasts
|
||||
.push(ToastKind::Info, "MCP server 开关在 P1 接 agent");
|
||||
this.sync();
|
||||
});
|
||||
}
|
||||
// P0 只保留入口:设置分组与 AI 端点暂不落状态(P1 接 setting schema)。
|
||||
ui.on_settings_section_changed(|_section| {});
|
||||
ui.on_ai_endpoint_changed(|_index| {});
|
||||
}
|
||||
|
||||
fn wire_overlays(ui: &crate::MainWindow, weak: &Weak<Inner>) {
|
||||
{
|
||||
let weak = weak.clone();
|
||||
ui.on_dialog_primary(move || close_current_dialog(&weak));
|
||||
}
|
||||
{
|
||||
let weak = weak.clone();
|
||||
ui.on_dialog_secondary(move || close_current_dialog(&weak));
|
||||
}
|
||||
{
|
||||
let weak = weak.clone();
|
||||
ui.on_dialog_dismissed(move || close_current_dialog(&weak));
|
||||
}
|
||||
{
|
||||
let weak = weak.clone();
|
||||
ui.on_toast_dismissed(move |id| {
|
||||
let Some(this) = weak.upgrade() else { return };
|
||||
if let Ok(id) = id.parse::<u64>() {
|
||||
this.state.borrow_mut().toasts.dismiss(id);
|
||||
this.sync();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn close_current_dialog(weak: &Weak<Inner>) {
|
||||
let Some(this) = weak.upgrade() else { return };
|
||||
this.state.borrow_mut().dialogs.close_current();
|
||||
this.sync();
|
||||
}
|
||||
Reference in new issue
Block a user