mod management;
mod persistence;
mod rendering;
pub use persistence::{format_window_title, load_project, save_project};
use super::behavior::TileBehavior;
use super::config::MosaicConfig;
use super::modal::Modals;
use super::project::WindowLayout;
use super::widget::{Pane, Widget};
use crate::prelude::*;
pub enum LayoutEvent {
Switched(String),
Created(String),
Saved(String),
Deleted(String),
Reset,
Renamed(String),
}
pub struct Mosaic<W: Widget<C, M>, C = (), M = ()> {
tile_tree: Option<egui_tiles::Tree<Pane<W>>>,
pub config: MosaicConfig,
pub(crate) behavior: TileBehavior<W, C, M>,
pub(crate) modals: Modals,
layout_modified: bool,
pub layout_name: String,
pub title: String,
viewport_texture_overrides: Option<Vec<egui::TextureId>>,
pub window_index: Option<usize>,
pub is_active_window: bool,
pending_messages: Vec<M>,
incoming_messages: std::collections::HashMap<egui_tiles::TileId, Vec<M>>,
layouts: Vec<WindowLayout<W>>,
active_layout_index: usize,
editing_layout_index: Option<usize>,
layout_name_edit_buffer: String,
}
impl<W: Widget<C, M>, C, M> Default for Mosaic<W, C, M> {
fn default() -> Self {
Self {
tile_tree: None,
config: MosaicConfig::default(),
behavior: TileBehavior::default(),
modals: Modals::default(),
layout_modified: false,
layout_name: String::new(),
title: String::new(),
viewport_texture_overrides: None,
window_index: None,
is_active_window: true,
pending_messages: Vec::new(),
incoming_messages: std::collections::HashMap::new(),
layouts: Vec::new(),
active_layout_index: 0,
editing_layout_index: None,
layout_name_edit_buffer: String::new(),
}
}
}
impl<W: Widget<C, M>, C, M> Mosaic<W, C, M> {
pub fn new() -> Self {
Self::default()
}
pub fn with_tree(tree: egui_tiles::Tree<Pane<W>>) -> Self {
Self {
tile_tree: Some(tree),
..Self::default()
}
}
pub fn with_panes(panes: Vec<W>) -> Self {
let mut tiles = egui_tiles::Tiles::default();
let pane_ids: Vec<_> = panes
.into_iter()
.map(|widget| tiles.insert_pane(Pane::new(widget)))
.collect();
let root = tiles.insert_tab_tile(pane_ids);
let tree = egui_tiles::Tree::new("mosaic_tree", root, tiles);
Self::with_tree(tree)
}
pub fn with_config(mut self, config: MosaicConfig) -> Self {
self.config = config;
self
}
pub fn with_title(mut self, title: impl Into<String>) -> Self {
self.title = title.into();
self
}
pub fn set_tree(&mut self, tree: egui_tiles::Tree<Pane<W>>) {
self.tile_tree = Some(tree);
self.layout_modified = true;
}
pub fn current_tree(&self) -> Option<&egui_tiles::Tree<Pane<W>>> {
self.tile_tree.as_ref()
}
pub fn from_window_layout(layout: WindowLayout<W>) -> Self {
Self {
tile_tree: Some(layout.tree),
layout_name: layout.layout_name,
..Self::default()
}
}
pub fn layout_modified(&self) -> bool {
self.layout_modified
}
pub fn take_layout_modified(&mut self) -> bool {
let modified = self.layout_modified;
self.layout_modified = false;
modified
}
pub fn viewport_rects(&self) -> &std::collections::HashMap<egui_tiles::TileId, egui::Rect> {
&self.behavior.viewport_rects
}
pub fn selected_viewport_tile(&self) -> Option<egui_tiles::TileId> {
self.behavior.selected_viewport_tile
}
pub fn modals(&mut self) -> &mut Modals {
&mut self.modals
}
pub fn clear_required_cameras(world: &mut World) {
world.resources.user_interface.required_cameras.clear();
world.resources.user_interface.required_camera_sizes.clear();
}
pub fn set_viewport_textures(&mut self, textures: Vec<egui::TextureId>) {
self.viewport_texture_overrides = Some(textures);
}
pub fn for_each_widget(&self, mut callback: impl FnMut(&W)) {
if let Some(tree) = &self.tile_tree {
for (_tile_id, tile) in tree.tiles.iter() {
if let egui_tiles::Tile::Pane(pane) = tile {
callback(&pane.widget);
}
}
}
}
pub fn for_each_widget_with_id(&self, mut callback: impl FnMut(egui_tiles::TileId, &W)) {
if let Some(tree) = &self.tile_tree {
for (tile_id, tile) in tree.tiles.iter() {
if let egui_tiles::Tile::Pane(pane) = tile {
callback(*tile_id, &pane.widget);
}
}
}
}
pub fn for_each_widget_mut(&mut self, mut callback: impl FnMut(&mut W)) {
if let Some(tree) = &mut self.tile_tree {
for (_tile_id, tile) in tree.tiles.iter_mut() {
if let egui_tiles::Tile::Pane(pane) = tile {
callback(&mut pane.widget);
}
}
}
}
pub fn for_each_widget_with_id_mut(
&mut self,
mut callback: impl FnMut(egui_tiles::TileId, &mut W),
) {
if let Some(tree) = &mut self.tile_tree {
for (tile_id, tile) in tree.tiles.iter_mut() {
if let egui_tiles::Tile::Pane(pane) = tile {
callback(*tile_id, &mut pane.widget);
}
}
}
}
pub fn widget_count(&self) -> usize {
self.tile_tree.as_ref().map_or(0, |tree| {
tree.tiles
.iter()
.filter(|(_, tile)| matches!(tile, egui_tiles::Tile::Pane(_)))
.count()
})
}
pub fn find_widget(&self, predicate: impl Fn(&W) -> bool) -> Option<egui_tiles::TileId> {
if let Some(tree) = &self.tile_tree {
for (tile_id, tile) in tree.tiles.iter() {
if let egui_tiles::Tile::Pane(pane) = tile
&& predicate(&pane.widget)
{
return Some(*tile_id);
}
}
}
None
}
pub fn get_widget(&self, tile_id: egui_tiles::TileId) -> Option<&W> {
if let Some(tree) = &self.tile_tree
&& let Some(egui_tiles::Tile::Pane(pane)) = tree.tiles.get(tile_id)
{
return Some(&pane.widget);
}
None
}
pub fn get_widget_mut(&mut self, tile_id: egui_tiles::TileId) -> Option<&mut W> {
if let Some(tree) = &mut self.tile_tree
&& let Some(egui_tiles::Tile::Pane(pane)) = tree.tiles.get_mut(tile_id)
{
return Some(&mut pane.widget);
}
None
}
pub fn activate_tab(&mut self, tile_id: egui_tiles::TileId) -> bool {
if let Some(tree) = &mut self.tile_tree {
tree.make_active(|id, _| id == tile_id)
} else {
false
}
}
pub fn insert_pane(&mut self, widget: W) -> Option<egui_tiles::TileId> {
if let Some(tree) = &mut self.tile_tree {
let new_tile_id = tree.tiles.insert_pane(Pane::new(widget));
if let Some(root) = tree.root {
Self::add_to_container(&mut tree.tiles, root, new_tile_id);
}
self.layout_modified = true;
Some(new_tile_id)
} else {
let mut tiles = egui_tiles::Tiles::default();
let pane_id = tiles.insert_pane(Pane::new(widget));
let root = tiles.insert_tab_tile(vec![pane_id]);
self.tile_tree = Some(egui_tiles::Tree::new("mosaic_tree", root, tiles));
self.layout_modified = true;
Some(pane_id)
}
}
pub fn insert_pane_in(
&mut self,
container_id: egui_tiles::TileId,
widget: W,
) -> Option<egui_tiles::TileId> {
if let Some(tree) = &mut self.tile_tree {
let new_tile_id = tree.tiles.insert_pane(Pane::new(widget));
Self::add_to_container(&mut tree.tiles, container_id, new_tile_id);
self.layout_modified = true;
Some(new_tile_id)
} else {
None
}
}
pub fn remove_pane(&mut self, tile_id: egui_tiles::TileId) -> Option<W> {
if let Some(tree) = &mut self.tile_tree
&& let Some(egui_tiles::Tile::Pane(pane)) = tree.tiles.remove(tile_id)
{
self.layout_modified = true;
return Some(pane.widget);
}
None
}
pub(crate) fn add_to_container(
tiles: &mut egui_tiles::Tiles<Pane<W>>,
container_id: egui_tiles::TileId,
child_id: egui_tiles::TileId,
) {
match tiles.get_mut(container_id) {
Some(egui_tiles::Tile::Container(egui_tiles::Container::Tabs(tabs))) => {
tabs.add_child(child_id);
tabs.set_active(child_id);
}
Some(egui_tiles::Tile::Container(egui_tiles::Container::Linear(linear))) => {
linear.add_child(child_id);
}
Some(egui_tiles::Tile::Container(egui_tiles::Container::Grid(grid))) => {
grid.add_child(child_id);
}
_ => {}
}
}
pub fn drain_messages(&mut self) -> Vec<M> {
self.pending_messages.drain(..).collect()
}
pub fn send_to(&mut self, tile_id: egui_tiles::TileId, message: M) {
self.incoming_messages
.entry(tile_id)
.or_default()
.push(message);
}
pub fn broadcast(&mut self, message: M)
where
M: Clone,
{
if let Some(tree) = &self.tile_tree {
let pane_ids: Vec<egui_tiles::TileId> = tree
.tiles
.iter()
.filter_map(|(tile_id, tile)| {
matches!(tile, egui_tiles::Tile::Pane(_)).then_some(*tile_id)
})
.collect();
for tile_id in pane_ids {
self.incoming_messages
.entry(tile_id)
.or_default()
.push(message.clone());
}
}
}
pub fn send_matching(&mut self, predicate: impl Fn(&W) -> bool, message: M)
where
M: Clone,
{
if let Some(tree) = &self.tile_tree {
let matching_ids: Vec<egui_tiles::TileId> = tree
.tiles
.iter()
.filter_map(|(tile_id, tile)| {
if let egui_tiles::Tile::Pane(pane) = tile {
predicate(&pane.widget).then_some(*tile_id)
} else {
None
}
})
.collect();
for tile_id in matching_ids {
self.incoming_messages
.entry(tile_id)
.or_default()
.push(message.clone());
}
}
}
}