use gitkraft_gui::GitKraft;
use iced::Settings;
fn main() -> iced::Result {
tracing_subscriber::fmt::init();
let saved_layout = gitkraft_core::features::persistence::ops::get_saved_layout()
.ok()
.flatten()
.unwrap_or_default();
let window_size = iced::Size::new(
saved_layout.window_width.unwrap_or(1400.0),
saved_layout.window_height.unwrap_or(800.0),
);
let window_position = match (saved_layout.window_x, saved_layout.window_y) {
(Some(x), Some(y)) if x > -200.0 && y > -200.0 && x < 8000.0 && y < 8000.0 => {
iced::window::Position::Specific(iced::Point::new(x, y))
}
_ => iced::window::Position::Default,
};
iced::application(boot, GitKraft::update, GitKraft::view)
.title("GitKraft — Git IDE")
.theme(|state: &GitKraft| state.iced_theme())
.subscription(|state: &GitKraft| {
let keyboard = iced::event::listen_with(|event, status, _window| {
if let iced::Event::Keyboard(iced::keyboard::Event::ModifiersChanged(mods)) = event
{
return Some(gitkraft_gui::Message::ModifiersChanged(mods));
}
if let iced::Event::Window(iced::window::Event::Resized(size)) = &event {
return Some(gitkraft_gui::Message::WindowResized(
size.width,
size.height,
));
}
if let iced::Event::Window(iced::window::Event::Moved(point)) = &event {
return Some(gitkraft_gui::Message::WindowMoved(point.x, point.y));
}
if let iced::Event::Keyboard(iced::keyboard::Event::KeyPressed {
key,
modifiers,
..
}) = event
{
if modifiers.control() || modifiers.command() {
return handle_key_press(key, modifiers);
}
if modifiers.shift()
&& matches!(
key,
iced::keyboard::Key::Named(
iced::keyboard::key::Named::ArrowDown
| iced::keyboard::key::Named::ArrowUp
)
)
{
return handle_key_press(key, modifiers);
}
if let iced::event::Status::Ignored = status {
return handle_key_press(key, modifiers);
}
}
None
});
let repo_path = state.active_tab().repo_path.clone();
let git_watcher = git_watch_subscription(repo_path);
let any_loading = state.tabs.iter().any(|t| t.is_loading);
let animation_ticker = if any_loading {
iced::Subscription::run(animation_tick_stream)
} else {
iced::Subscription::none()
};
iced::Subscription::batch([keyboard, git_watcher, animation_ticker])
})
.scale_factor(|state: &GitKraft| state.ui_scale)
.settings(Settings {
fonts: vec![iced_fonts::BOOTSTRAP_FONT_BYTES.into()],
..Default::default()
})
.window(iced::window::Settings {
size: window_size,
position: window_position,
..Default::default()
})
.run()
}
fn boot() -> (GitKraft, iced::Task<gitkraft_gui::Message>) {
let (mut state, open_tabs) = GitKraft::new_with_session_paths();
let maximize_task: iced::Task<gitkraft_gui::Message> =
iced::window::oldest().and_then(|id| iced::window::maximize(id, true));
let restore_task = if !open_tabs.is_empty() {
let tasks: Vec<iced::Task<gitkraft_gui::Message>> = open_tabs
.iter()
.enumerate()
.filter(|(_, p)| p.exists())
.map(|(i, p)| gitkraft_gui::features::repo::commands::load_repo_at(i, p.clone()))
.collect();
iced::Task::batch(tasks)
} else {
match gitkraft_core::features::persistence::ops::get_last_repo() {
Ok(Some(path)) if path.exists() => {
let tab = state.active_tab_mut();
tab.is_loading = true;
tab.status_message = Some("Loading repository...".to_string());
gitkraft_gui::features::repo::commands::load_repo(path)
}
_ => iced::Task::none(),
}
};
(state, iced::Task::batch([maximize_task, restore_task]))
}
fn git_watch_subscription(
repo_path: Option<std::path::PathBuf>,
) -> iced::Subscription<gitkraft_gui::Message> {
let Some(path) = repo_path else {
return iced::Subscription::none();
};
iced::Subscription::run_with(path, git_watch_builder)
}
#[allow(clippy::ptr_arg)]
fn git_watch_builder(
path: &std::path::PathBuf,
) -> impl futures::Stream<Item = gitkraft_gui::Message> {
git_watch_stream(path.clone())
}
fn git_watch_stream(
repo_path: std::path::PathBuf,
) -> impl futures::Stream<Item = gitkraft_gui::Message> {
use std::sync::mpsc;
let (tx, rx) = mpsc::channel::<()>();
let git_dir = repo_path.join(".git");
gitkraft_core::spawn_git_watcher(git_dir, move || tx.send(()).is_ok());
futures::stream::unfold(rx, |rx| async move {
match rx.recv() {
Ok(()) => Some((gitkraft_gui::Message::FileSystemChanged, rx)),
Err(_) => None, }
})
}
fn animation_tick_stream() -> impl futures::Stream<Item = gitkraft_gui::Message> {
futures::stream::unfold((), |()| async {
std::thread::sleep(std::time::Duration::from_millis(100));
Some((gitkraft_gui::Message::AnimationTick, ()))
})
}
fn handle_key_press(
key: iced::keyboard::Key,
modifiers: iced::keyboard::Modifiers,
) -> Option<gitkraft_gui::Message> {
use iced::keyboard::Key;
use iced::keyboard::key::Named;
if modifiers.control() || modifiers.command() {
match key {
Key::Character(ref c) if c.as_str() == "+" || c.as_str() == "=" => {
Some(gitkraft_gui::Message::ZoomIn)
}
Key::Character(ref c) if c.as_str() == "-" => Some(gitkraft_gui::Message::ZoomOut),
Key::Character(ref c) if c.as_str() == "0" => Some(gitkraft_gui::Message::ZoomReset),
Key::Character(ref c) if c.as_str() == "f" => Some(gitkraft_gui::Message::ToggleSearch),
Key::Character(ref c) if c.as_str() == "," => {
Some(gitkraft_gui::Message::OpenSettingsFile)
}
_ => None,
}
} else if modifiers.shift() {
match key {
Key::Named(Named::ArrowDown) => Some(gitkraft_gui::Message::ShiftArrowDown),
Key::Named(Named::ArrowUp) => Some(gitkraft_gui::Message::ShiftArrowUp),
_ => None,
}
} else {
match key {
Key::Named(iced::keyboard::key::Named::Escape) => {
Some(gitkraft_gui::Message::CloseFileBlame)
}
_ => None,
}
}
}