mirror of
https://github.com/enso-org/enso.git
synced 2024-12-29 01:25:27 +03:00
Open Project Dialog (#5607)
Closes #5022 This is basically a reimplementation of the Open Project Dialog that was present in the IDE a while ago. Now it uses the modern shiny `grid-view` instead of the old rusty `list-view`. https://user-images.githubusercontent.com/6566674/219052041-ff99aa37-249c-4a63-93a5-5acd6b221dc8.mp4
This commit is contained in:
parent
172f72941b
commit
19beb01cf3
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -4166,6 +4166,7 @@ dependencies = [
|
|||||||
"enso-shapely",
|
"enso-shapely",
|
||||||
"ensogl",
|
"ensogl",
|
||||||
"ensogl-component",
|
"ensogl-component",
|
||||||
|
"ensogl-derive-theme",
|
||||||
"ensogl-gui-component",
|
"ensogl-gui-component",
|
||||||
"ensogl-hardcoded-theme",
|
"ensogl-hardcoded-theme",
|
||||||
"ensogl-text",
|
"ensogl-text",
|
||||||
|
@ -106,6 +106,8 @@ pub enum Notification {
|
|||||||
NewProjectCreated,
|
NewProjectCreated,
|
||||||
/// User opened an existing project.
|
/// User opened an existing project.
|
||||||
ProjectOpened,
|
ProjectOpened,
|
||||||
|
/// User closed the project.
|
||||||
|
ProjectClosed,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -141,6 +143,9 @@ pub trait ManagingProjectAPI {
|
|||||||
/// Open the project with given UUID.
|
/// Open the project with given UUID.
|
||||||
fn open_project(&self, id: Uuid) -> BoxFuture<FallibleResult>;
|
fn open_project(&self, id: Uuid) -> BoxFuture<FallibleResult>;
|
||||||
|
|
||||||
|
/// Close the currently opened project. Does nothing if no project is open.
|
||||||
|
fn close_project(&self);
|
||||||
|
|
||||||
/// Open project by name. It makes two calls to the Project Manager: one for listing projects
|
/// Open project by name. It makes two calls to the Project Manager: one for listing projects
|
||||||
/// and then for the project opening.
|
/// and then for the project opening.
|
||||||
fn open_project_by_name(&self, name: String) -> BoxFuture<FallibleResult> {
|
fn open_project_by_name(&self, name: String) -> BoxFuture<FallibleResult> {
|
||||||
|
@ -92,6 +92,7 @@ impl API for Handle {
|
|||||||
fn current_project(&self) -> Option<model::Project> {
|
fn current_project(&self) -> Option<model::Project> {
|
||||||
self.current_project.get()
|
self.current_project.get()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn status_notifications(&self) -> &StatusNotificationPublisher {
|
fn status_notifications(&self) -> &StatusNotificationPublisher {
|
||||||
&self.status_notifications
|
&self.status_notifications
|
||||||
}
|
}
|
||||||
@ -132,13 +133,17 @@ impl ManagingProjectAPI for Handle {
|
|||||||
let project_mgr = self.project_manager.clone_ref();
|
let project_mgr = self.project_manager.clone_ref();
|
||||||
let new_project = Project::new_opened(project_mgr, new_project_id);
|
let new_project = Project::new_opened(project_mgr, new_project_id);
|
||||||
self.current_project.set(Some(new_project.await?));
|
self.current_project.set(Some(new_project.await?));
|
||||||
let notify = self.notifications.publish(Notification::NewProjectCreated);
|
self.notifications.notify(Notification::NewProjectCreated);
|
||||||
executor::global::spawn(notify);
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
.boxed_local()
|
.boxed_local()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn close_project(&self) {
|
||||||
|
self.current_project.set(None);
|
||||||
|
self.notifications.notify(Notification::ProjectClosed);
|
||||||
|
}
|
||||||
|
|
||||||
#[profile(Objective)]
|
#[profile(Objective)]
|
||||||
fn list_projects(&self) -> BoxFuture<FallibleResult<Vec<ProjectMetadata>>> {
|
fn list_projects(&self) -> BoxFuture<FallibleResult<Vec<ProjectMetadata>>> {
|
||||||
async move { Ok(self.project_manager.list_projects(&None).await?.projects) }.boxed_local()
|
async move { Ok(self.project_manager.list_projects(&None).await?.projects) }.boxed_local()
|
||||||
@ -150,7 +155,7 @@ impl ManagingProjectAPI for Handle {
|
|||||||
let project_mgr = self.project_manager.clone_ref();
|
let project_mgr = self.project_manager.clone_ref();
|
||||||
let new_project = model::project::Synchronized::new_opened(project_mgr, id);
|
let new_project = model::project::Synchronized::new_opened(project_mgr, id);
|
||||||
self.current_project.set(Some(new_project.await?));
|
self.current_project.set(Some(new_project.await?));
|
||||||
executor::global::spawn(self.notifications.publish(Notification::ProjectOpened));
|
self.notifications.notify(Notification::ProjectOpened);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
.boxed_local()
|
.boxed_local()
|
||||||
|
@ -82,6 +82,13 @@ impl Model {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn close_project(&self) {
|
||||||
|
*self.current_project.borrow_mut() = None;
|
||||||
|
// Clear the graph editor so that it will not display any nodes from the previous
|
||||||
|
// project when the new project is loaded.
|
||||||
|
self.view.project().graph().remove_all_nodes();
|
||||||
|
}
|
||||||
|
|
||||||
/// Open a project by name. It makes two calls to Project Manager: one for listing projects and
|
/// Open a project by name. It makes two calls to Project Manager: one for listing projects and
|
||||||
/// a second one for opening the project.
|
/// a second one for opening the project.
|
||||||
#[profile(Task)]
|
#[profile(Task)]
|
||||||
@ -211,6 +218,9 @@ impl Presenter {
|
|||||||
controller::ide::Notification::NewProjectCreated
|
controller::ide::Notification::NewProjectCreated
|
||||||
| controller::ide::Notification::ProjectOpened =>
|
| controller::ide::Notification::ProjectOpened =>
|
||||||
model.setup_and_display_new_project(),
|
model.setup_and_display_new_project(),
|
||||||
|
controller::ide::Notification::ProjectClosed => {
|
||||||
|
model.close_project();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
futures::future::ready(())
|
futures::future::ready(())
|
||||||
});
|
});
|
||||||
|
@ -8,6 +8,7 @@ use crate::presenter;
|
|||||||
use crate::presenter::graph::ViewNodeId;
|
use crate::presenter::graph::ViewNodeId;
|
||||||
|
|
||||||
use enso_frp as frp;
|
use enso_frp as frp;
|
||||||
|
use ensogl::system::js;
|
||||||
use ide_view as view;
|
use ide_view as view;
|
||||||
use ide_view::project::SearcherParams;
|
use ide_view::project::SearcherParams;
|
||||||
use model::module::NotificationKind;
|
use model::module::NotificationKind;
|
||||||
@ -16,6 +17,16 @@ use model::project::VcsStatus;
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// =================
|
||||||
|
// === Constants ===
|
||||||
|
// =================
|
||||||
|
|
||||||
|
/// We don't know how long the project opening will take, but we still want to show a fake progress
|
||||||
|
/// indicator for the user. This constant represents a progress percentage that will be displayed.
|
||||||
|
const OPEN_PROJECT_SPINNER_PROGRESS: f32 = 0.8;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// =============
|
// =============
|
||||||
// === Model ===
|
// === Model ===
|
||||||
// =============
|
// =============
|
||||||
@ -24,15 +35,16 @@ use model::project::VcsStatus;
|
|||||||
#[allow(unused)]
|
#[allow(unused)]
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct Model {
|
struct Model {
|
||||||
controller: controller::Project,
|
controller: controller::Project,
|
||||||
module_model: model::Module,
|
module_model: model::Module,
|
||||||
graph_controller: controller::ExecutedGraph,
|
graph_controller: controller::ExecutedGraph,
|
||||||
ide_controller: controller::Ide,
|
ide_controller: controller::Ide,
|
||||||
view: view::project::View,
|
view: view::project::View,
|
||||||
status_bar: view::status_bar::View,
|
status_bar: view::status_bar::View,
|
||||||
graph: presenter::Graph,
|
graph: presenter::Graph,
|
||||||
code: presenter::Code,
|
code: presenter::Code,
|
||||||
searcher: RefCell<Option<presenter::Searcher>>,
|
searcher: RefCell<Option<presenter::Searcher>>,
|
||||||
|
available_projects: Rc<RefCell<Vec<(ImString, Uuid)>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Model {
|
impl Model {
|
||||||
@ -54,6 +66,7 @@ impl Model {
|
|||||||
);
|
);
|
||||||
let code = presenter::Code::new(text_controller, &view);
|
let code = presenter::Code::new(text_controller, &view);
|
||||||
let searcher = default();
|
let searcher = default();
|
||||||
|
let available_projects = default();
|
||||||
Model {
|
Model {
|
||||||
controller,
|
controller,
|
||||||
module_model,
|
module_model,
|
||||||
@ -64,6 +77,7 @@ impl Model {
|
|||||||
graph,
|
graph,
|
||||||
code,
|
code,
|
||||||
searcher,
|
searcher,
|
||||||
|
available_projects,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -213,8 +227,53 @@ impl Model {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
/// Prepare a list of projects to display in the Open Project dialog.
|
||||||
|
fn project_list_opened(&self, project_list_ready: frp::Source<()>) {
|
||||||
|
let controller = self.ide_controller.clone_ref();
|
||||||
|
let projects_list = self.available_projects.clone_ref();
|
||||||
|
executor::global::spawn(async move {
|
||||||
|
if let Ok(api) = controller.manage_projects() {
|
||||||
|
if let Ok(projects) = api.list_projects().await {
|
||||||
|
let projects = projects.into_iter();
|
||||||
|
let projects = projects.map(|p| (p.name.clone().into(), p.id)).collect_vec();
|
||||||
|
*projects_list.borrow_mut() = projects;
|
||||||
|
project_list_ready.emit(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// User clicked a project in the Open Project dialog. Open it.
|
||||||
|
fn open_project(&self, id_in_list: &usize) {
|
||||||
|
let controller = self.ide_controller.clone_ref();
|
||||||
|
let projects_list = self.available_projects.clone_ref();
|
||||||
|
let view = self.view.clone_ref();
|
||||||
|
let status_bar = self.status_bar.clone_ref();
|
||||||
|
let id = *id_in_list;
|
||||||
|
executor::global::spawn(async move {
|
||||||
|
let app = js::app_or_panic();
|
||||||
|
app.show_progress_indicator(OPEN_PROJECT_SPINNER_PROGRESS);
|
||||||
|
view.hide_graph_editor();
|
||||||
|
if let Ok(api) = controller.manage_projects() {
|
||||||
|
api.close_project();
|
||||||
|
let uuid = projects_list.borrow().get(id).map(|(_name, uuid)| *uuid);
|
||||||
|
if let Some(uuid) = uuid {
|
||||||
|
if let Err(error) = api.open_project(uuid).await {
|
||||||
|
error!("Error opening project: {error}.");
|
||||||
|
status_bar.add_event(format!("Error opening project: {error}."));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
error!("Project with id {id} not found.");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
error!("Project Manager API not available, cannot open project.");
|
||||||
|
}
|
||||||
|
app.hide_progress_indicator();
|
||||||
|
view.show_graph_editor();
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// ===============
|
// ===============
|
||||||
@ -255,8 +314,29 @@ impl Project {
|
|||||||
let view = &model.view.frp;
|
let view = &model.view.frp;
|
||||||
let breadcrumbs = &model.view.graph().model.breadcrumbs;
|
let breadcrumbs = &model.view.graph().model.breadcrumbs;
|
||||||
let graph_view = &model.view.graph().frp;
|
let graph_view = &model.view.graph().frp;
|
||||||
|
let project_list = &model.view.project_list();
|
||||||
|
|
||||||
frp::extend! { network
|
frp::extend! { network
|
||||||
|
project_list_ready <- source_();
|
||||||
|
|
||||||
|
project_list.grid.reset_entries <+ project_list_ready.map(f_!([model]{
|
||||||
|
let cols = 1;
|
||||||
|
let rows = model.available_projects.borrow().len();
|
||||||
|
(rows, cols)
|
||||||
|
}));
|
||||||
|
entry_model <- project_list.grid.model_for_entry_needed.map(f!([model]((row, col)) {
|
||||||
|
let projects = model.available_projects.borrow();
|
||||||
|
let project = projects.get(*row);
|
||||||
|
project.map(|(name, _)| (*row, *col, name.clone_ref()))
|
||||||
|
})).filter_map(|t| t.clone());
|
||||||
|
project_list.grid.model_for_entry <+ entry_model;
|
||||||
|
|
||||||
|
open_project_list <- view.project_list_shown.on_true();
|
||||||
|
eval_ open_project_list(model.project_list_opened(project_list_ready.clone_ref()));
|
||||||
|
selected_project <- project_list.grid.entry_selected.filter_map(|e| *e);
|
||||||
|
eval selected_project(((row, _col)) model.open_project(row));
|
||||||
|
project_list.grid.select_entry <+ selected_project.constant(None);
|
||||||
|
|
||||||
eval view.searcher ([model](params) {
|
eval view.searcher ([model](params) {
|
||||||
if let Some(params) = params {
|
if let Some(params) = params {
|
||||||
model.setup_searcher_presenter(*params)
|
model.setup_searcher_presenter(*params)
|
||||||
|
@ -17,6 +17,7 @@ enso-shapely = { path = "../../../lib/rust/shapely" }
|
|||||||
engine-protocol = { path = "../controller/engine-protocol" }
|
engine-protocol = { path = "../controller/engine-protocol" }
|
||||||
ensogl = { path = "../../../lib/rust/ensogl" }
|
ensogl = { path = "../../../lib/rust/ensogl" }
|
||||||
ensogl-component = { path = "../../../lib/rust/ensogl/component" }
|
ensogl-component = { path = "../../../lib/rust/ensogl/component" }
|
||||||
|
ensogl-derive-theme = { path = "../../../lib/rust/ensogl/app/theme/derive" }
|
||||||
ensogl-gui-component = { path = "../../../lib/rust/ensogl/component/gui" }
|
ensogl-gui-component = { path = "../../../lib/rust/ensogl/component/gui" }
|
||||||
ensogl-text = { path = "../../../lib/rust/ensogl/component/text" }
|
ensogl-text = { path = "../../../lib/rust/ensogl/component/text" }
|
||||||
ensogl-text-msdf = { path = "../../../lib/rust/ensogl/component/text/src/font/msdf" }
|
ensogl-text-msdf = { path = "../../../lib/rust/ensogl/component/text/src/font/msdf" }
|
||||||
|
@ -33,8 +33,8 @@
|
|||||||
pub mod code_editor;
|
pub mod code_editor;
|
||||||
pub mod component_browser;
|
pub mod component_browser;
|
||||||
pub mod debug_mode_popup;
|
pub mod debug_mode_popup;
|
||||||
pub mod open_dialog;
|
|
||||||
pub mod project;
|
pub mod project;
|
||||||
|
pub mod project_list;
|
||||||
pub mod root;
|
pub mod root;
|
||||||
pub mod searcher;
|
pub mod searcher;
|
||||||
pub mod status_bar;
|
pub mod status_bar;
|
||||||
|
@ -1,80 +0,0 @@
|
|||||||
//! A module with [`OpenDialog`] component.
|
|
||||||
|
|
||||||
use crate::prelude::*;
|
|
||||||
|
|
||||||
use enso_frp as frp;
|
|
||||||
use ensogl::application::Application;
|
|
||||||
use ensogl::display;
|
|
||||||
use ensogl::display::shape::StyleWatchFrp;
|
|
||||||
use ensogl_component::file_browser::FileBrowser;
|
|
||||||
use ensogl_hardcoded_theme as theme;
|
|
||||||
|
|
||||||
|
|
||||||
// ==============
|
|
||||||
// === Export ===
|
|
||||||
// ==============
|
|
||||||
|
|
||||||
pub mod project_list;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// ==================
|
|
||||||
// === OpenDialog ===
|
|
||||||
// ==================
|
|
||||||
|
|
||||||
/// An Open Dialog GUI component.
|
|
||||||
///
|
|
||||||
/// This is component bounding together projects-to-open list and the file browser. It does not
|
|
||||||
/// provide frp endpoints by its own: you should just go directly to the `project_list` or
|
|
||||||
/// `file_browser` field.
|
|
||||||
#[allow(missing_docs)]
|
|
||||||
#[derive(Clone, CloneRef, Debug)]
|
|
||||||
pub struct OpenDialog {
|
|
||||||
network: frp::Network,
|
|
||||||
pub project_list: project_list::ProjectList,
|
|
||||||
pub file_browser: FileBrowser,
|
|
||||||
display_object: display::object::Instance,
|
|
||||||
style_watch: StyleWatchFrp,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl OpenDialog {
|
|
||||||
/// Create Open Dialog component.
|
|
||||||
pub fn new(app: &Application) -> Self {
|
|
||||||
let network = frp::Network::new("OpenDialog");
|
|
||||||
let style_watch = StyleWatchFrp::new(&app.display.default_scene.style_sheet);
|
|
||||||
let project_list = project_list::ProjectList::new(app);
|
|
||||||
let file_browser = FileBrowser::new();
|
|
||||||
// Once FileBrowser will be implemented as component, it should be instantiated this way:
|
|
||||||
//let file_browser = app.new_view::<FileBrowser>();
|
|
||||||
|
|
||||||
let display_object = display::object::Instance::new();
|
|
||||||
|
|
||||||
display_object.add_child(&project_list);
|
|
||||||
display_object.add_child(&file_browser);
|
|
||||||
app.display.default_scene.layers.panel.add(&display_object);
|
|
||||||
|
|
||||||
use theme::application as theme_app;
|
|
||||||
let project_list_width = style_watch.get_number(theme_app::project_list::width);
|
|
||||||
let file_browser_width = style_watch.get_number(theme_app::file_browser::width);
|
|
||||||
let gap_between_panels = style_watch.get_number(theme_app::open_dialog::gap_between_panels);
|
|
||||||
frp::extend! { network
|
|
||||||
init <- source::<()>();
|
|
||||||
width <- all_with4(&project_list_width,&file_browser_width,&gap_between_panels,&init,
|
|
||||||
|pw,fw,g,()| *pw + *g + *fw
|
|
||||||
);
|
|
||||||
project_list_x <- all_with(&width,&project_list_width,|w,pw| - *w / 2.0 + *pw / 2.0);
|
|
||||||
file_browser_x <- all_with(&width,&file_browser_width, |w,fw| w / 2.0 - *fw / 2.0);
|
|
||||||
|
|
||||||
eval project_list_x ((x) project_list.set_x(*x));
|
|
||||||
eval file_browser_x ((x) file_browser.set_x(*x));
|
|
||||||
}
|
|
||||||
init.emit(());
|
|
||||||
Self { network, project_list, file_browser, display_object, style_watch }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl display::Object for OpenDialog {
|
|
||||||
fn display_object(&self) -> &display::object::Instance {
|
|
||||||
&self.display_object
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,146 +0,0 @@
|
|||||||
//! A module containing [`ProjectList`] component and all related structures.
|
|
||||||
|
|
||||||
use crate::prelude::*;
|
|
||||||
use ensogl::display::shape::*;
|
|
||||||
|
|
||||||
use enso_frp as frp;
|
|
||||||
use ensogl::application::Application;
|
|
||||||
use ensogl::display;
|
|
||||||
use ensogl_component::list_view;
|
|
||||||
use ensogl_component::shadow;
|
|
||||||
use ensogl_hardcoded_theme::application::project_list as theme;
|
|
||||||
use ensogl_text as text;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// =============
|
|
||||||
// === Entry ===
|
|
||||||
// =============
|
|
||||||
|
|
||||||
/// The entry in project list.
|
|
||||||
pub type Entry = list_view::entry::Label;
|
|
||||||
|
|
||||||
|
|
||||||
// ==============
|
|
||||||
// === Shapes ===
|
|
||||||
// ==============
|
|
||||||
|
|
||||||
mod background {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
pub const SHADOW_PX: f32 = 10.0;
|
|
||||||
pub const CORNER_RADIUS_PX: f32 = 16.0;
|
|
||||||
|
|
||||||
ensogl::shape! {
|
|
||||||
(style:Style) {
|
|
||||||
let sprite_width : Var<Pixels> = "input_size.x".into();
|
|
||||||
let sprite_height : Var<Pixels> = "input_size.y".into();
|
|
||||||
let width = sprite_width - SHADOW_PX.px() * 2.0;
|
|
||||||
let height = sprite_height - SHADOW_PX.px() * 2.0;
|
|
||||||
let color = style.get_color(theme::background);
|
|
||||||
let border_size = style.get_number(theme::bar::border_size);
|
|
||||||
let bar_height = style.get_number(theme::bar::height);
|
|
||||||
let rect = Rect((&width,&height)).corners_radius(CORNER_RADIUS_PX.px());
|
|
||||||
let shape = rect.fill(color);
|
|
||||||
|
|
||||||
let shadow = shadow::from_shape(rect.into(),style);
|
|
||||||
|
|
||||||
let toolbar_border = Rect((width, border_size.px()))
|
|
||||||
.translate_y(height / 2.0 - bar_height.px())
|
|
||||||
.fill(style.get_color(theme::bar::border_color));
|
|
||||||
|
|
||||||
(shadow + shape + toolbar_border).into()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// ===================
|
|
||||||
// === ProjectList ===
|
|
||||||
// ===================
|
|
||||||
|
|
||||||
/// The Project List GUI Component.
|
|
||||||
///
|
|
||||||
/// This is a list of projects in a nice frame with title.
|
|
||||||
#[derive(Clone, CloneRef, Debug)]
|
|
||||||
pub struct ProjectList {
|
|
||||||
network: frp::Network,
|
|
||||||
display_object: display::object::Instance,
|
|
||||||
background: background::View, //TODO[ao] use Card instead.
|
|
||||||
caption: text::Text,
|
|
||||||
list: list_view::ListView<Entry>,
|
|
||||||
style_watch: StyleWatchFrp,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Deref for ProjectList {
|
|
||||||
type Target = list_view::Frp<Entry>;
|
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
|
||||||
&self.list.frp
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ProjectList {
|
|
||||||
/// Create Project List Component.
|
|
||||||
pub fn new(app: &Application) -> Self {
|
|
||||||
let network = frp::Network::new("ProjectList");
|
|
||||||
let display_object = display::object::Instance::new();
|
|
||||||
let background = background::View::new();
|
|
||||||
let caption = app.new_view::<text::Text>();
|
|
||||||
let list = app.new_view::<list_view::ListView<Entry>>();
|
|
||||||
display_object.add_child(&background);
|
|
||||||
display_object.add_child(&caption);
|
|
||||||
display_object.add_child(&list);
|
|
||||||
app.display.default_scene.layers.panel.add(&display_object);
|
|
||||||
caption.set_content("Open Project");
|
|
||||||
caption.add_to_scene_layer(&app.display.default_scene.layers.panel_text);
|
|
||||||
list.set_label_layer(&app.display.default_scene.layers.panel_text);
|
|
||||||
|
|
||||||
ensogl::shapes_order_dependencies! {
|
|
||||||
app.display.default_scene => {
|
|
||||||
background -> list_view::selection;
|
|
||||||
list_view::background -> background;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let style_watch = StyleWatchFrp::new(&app.display.default_scene.style_sheet);
|
|
||||||
let width = style_watch.get_number(theme::width);
|
|
||||||
let height = style_watch.get_number(theme::height);
|
|
||||||
let bar_height = style_watch.get_number(theme::bar::height);
|
|
||||||
let padding = style_watch.get_number(theme::padding);
|
|
||||||
let color = style_watch.get_color(theme::bar::label::color);
|
|
||||||
let label_size = style_watch.get_number(theme::bar::label::size);
|
|
||||||
|
|
||||||
frp::extend! { network
|
|
||||||
init <- source::<()>();
|
|
||||||
size <- all_with3(&width,&height,&init,|w,h,()|
|
|
||||||
Vector2(w + background::SHADOW_PX * 2.0,h + background::SHADOW_PX * 2.0)
|
|
||||||
);
|
|
||||||
list_size <- all_with4(&width,&height,&bar_height,&init,|w,h,bh,()|
|
|
||||||
Vector2(*w,*h - *bh));
|
|
||||||
list_y <- all_with(&bar_height,&init, |bh,()| -*bh / 2.0);
|
|
||||||
caption_xy <- all_with4(&width,&height,&padding,&init,
|
|
||||||
|w,h,p,()| Vector2(-*w / 2.0 + *p, *h / 2.0 - p)
|
|
||||||
);
|
|
||||||
color <- all(&color,&init)._0();
|
|
||||||
label_size <- all(&label_size,&init)._0();
|
|
||||||
|
|
||||||
eval size ((size) background.set_size(*size););
|
|
||||||
eval list_size ((size) list.resize(*size));
|
|
||||||
eval list_y ((y) list.set_y(*y));
|
|
||||||
eval caption_xy ((xy) caption.set_xy(*xy));
|
|
||||||
eval color ((color) caption.set_property_default(color));
|
|
||||||
eval label_size ((size) caption.set_property_default(text::Size(*size)));
|
|
||||||
};
|
|
||||||
init.emit(());
|
|
||||||
|
|
||||||
Self { network, display_object, background, caption, list, style_watch }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl display::Object for ProjectList {
|
|
||||||
fn display_object(&self) -> &display::object::Instance {
|
|
||||||
&self.display_object
|
|
||||||
}
|
|
||||||
}
|
|
@ -15,7 +15,7 @@ use crate::graph_editor::component::node::Expression;
|
|||||||
use crate::graph_editor::component::visualization;
|
use crate::graph_editor::component::visualization;
|
||||||
use crate::graph_editor::GraphEditor;
|
use crate::graph_editor::GraphEditor;
|
||||||
use crate::graph_editor::NodeId;
|
use crate::graph_editor::NodeId;
|
||||||
use crate::open_dialog::OpenDialog;
|
use crate::project_list::ProjectList;
|
||||||
use crate::searcher;
|
use crate::searcher;
|
||||||
|
|
||||||
use enso_config::ARGS;
|
use enso_config::ARGS;
|
||||||
@ -69,12 +69,16 @@ impl SearcherParams {
|
|||||||
|
|
||||||
ensogl::define_endpoints! {
|
ensogl::define_endpoints! {
|
||||||
Input {
|
Input {
|
||||||
/// Open the Open File or Project Dialog.
|
/// Open the Open Project Dialog.
|
||||||
show_open_dialog(),
|
show_project_list(),
|
||||||
|
/// Close the Open Project Dialog without further action
|
||||||
|
hide_project_list(),
|
||||||
/// Close the searcher without taking any actions
|
/// Close the searcher without taking any actions
|
||||||
close_searcher(),
|
close_searcher(),
|
||||||
/// Close the Open File or Project Dialog without further action
|
/// Show the graph editor.
|
||||||
close_open_dialog(),
|
show_graph_editor(),
|
||||||
|
/// Hide the graph editor.
|
||||||
|
hide_graph_editor(),
|
||||||
/// Simulates a style toggle press event.
|
/// Simulates a style toggle press event.
|
||||||
toggle_style(),
|
toggle_style(),
|
||||||
/// Saves a snapshot of the current state of the project to the VCS.
|
/// Saves a snapshot of the current state of the project to the VCS.
|
||||||
@ -110,7 +114,7 @@ ensogl::define_endpoints! {
|
|||||||
editing_aborted (NodeId),
|
editing_aborted (NodeId),
|
||||||
editing_committed_old_searcher (NodeId, Option<searcher::entry::Id>),
|
editing_committed_old_searcher (NodeId, Option<searcher::entry::Id>),
|
||||||
editing_committed (NodeId, Option<component_list_panel::grid::GroupEntryId>),
|
editing_committed (NodeId, Option<component_list_panel::grid::GroupEntryId>),
|
||||||
open_dialog_shown (bool),
|
project_list_shown (bool),
|
||||||
code_editor_shown (bool),
|
code_editor_shown (bool),
|
||||||
style (Theme),
|
style (Theme),
|
||||||
fullscreen_visualization_shown (bool),
|
fullscreen_visualization_shown (bool),
|
||||||
@ -248,7 +252,7 @@ struct Model {
|
|||||||
searcher: SearcherVariant,
|
searcher: SearcherVariant,
|
||||||
code_editor: code_editor::View,
|
code_editor: code_editor::View,
|
||||||
fullscreen_vis: Rc<RefCell<Option<visualization::fullscreen::Panel>>>,
|
fullscreen_vis: Rc<RefCell<Option<visualization::fullscreen::Panel>>>,
|
||||||
open_dialog: Rc<OpenDialog>,
|
project_list: Rc<ProjectList>,
|
||||||
debug_mode_popup: debug_mode_popup::View,
|
debug_mode_popup: debug_mode_popup::View,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -269,7 +273,7 @@ impl Model {
|
|||||||
window_control_buttons
|
window_control_buttons
|
||||||
});
|
});
|
||||||
let window_control_buttons = Immutable(window_control_buttons);
|
let window_control_buttons = Immutable(window_control_buttons);
|
||||||
let open_dialog = Rc::new(OpenDialog::new(app));
|
let project_list = Rc::new(ProjectList::new(app));
|
||||||
|
|
||||||
display_object.add_child(&graph_editor);
|
display_object.add_child(&graph_editor);
|
||||||
display_object.add_child(&code_editor);
|
display_object.add_child(&code_editor);
|
||||||
@ -287,7 +291,7 @@ impl Model {
|
|||||||
searcher,
|
searcher,
|
||||||
code_editor,
|
code_editor,
|
||||||
fullscreen_vis,
|
fullscreen_vis,
|
||||||
open_dialog,
|
project_list,
|
||||||
debug_mode_popup,
|
debug_mode_popup,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -377,12 +381,20 @@ impl Model {
|
|||||||
js::fullscreen();
|
js::fullscreen();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn show_open_dialog(&self) {
|
fn show_project_list(&self) {
|
||||||
self.display_object.add_child(&*self.open_dialog);
|
self.display_object.add_child(&*self.project_list);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn hide_open_dialog(&self) {
|
fn hide_project_list(&self) {
|
||||||
self.display_object.remove_child(&*self.open_dialog);
|
self.display_object.remove_child(&*self.project_list);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn show_graph_editor(&self) {
|
||||||
|
self.display_object.add_child(&*self.graph_editor);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn hide_graph_editor(&self) {
|
||||||
|
self.display_object.remove_child(&*self.graph_editor);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -458,8 +470,7 @@ impl View {
|
|||||||
let network = &frp.network;
|
let network = &frp.network;
|
||||||
let searcher = &model.searcher.frp(network);
|
let searcher = &model.searcher.frp(network);
|
||||||
let graph = &model.graph_editor.frp;
|
let graph = &model.graph_editor.frp;
|
||||||
let project_list = &model.open_dialog.project_list;
|
let project_list = &model.project_list;
|
||||||
let file_browser = &model.open_dialog.file_browser;
|
|
||||||
let searcher_anchor = DEPRECATED_Animation::<Vector2<f32>>::new(network);
|
let searcher_anchor = DEPRECATED_Animation::<Vector2<f32>>::new(network);
|
||||||
|
|
||||||
// FIXME[WD]: Think how to refactor it, as it needs to be done before model, as we do not
|
// FIXME[WD]: Think how to refactor it, as it needs to be done before model, as we do not
|
||||||
@ -484,6 +495,9 @@ impl View {
|
|||||||
frp::extend! { network
|
frp::extend! { network
|
||||||
eval shape ((shape) model.on_dom_shape_changed(shape));
|
eval shape ((shape) model.on_dom_shape_changed(shape));
|
||||||
|
|
||||||
|
eval_ frp.show_graph_editor(model.show_graph_editor());
|
||||||
|
eval_ frp.hide_graph_editor(model.hide_graph_editor());
|
||||||
|
|
||||||
// === Searcher Position and Size ===
|
// === Searcher Position and Size ===
|
||||||
|
|
||||||
let main_cam = app.display.default_scene.layers.main.camera();
|
let main_cam = app.display.default_scene.layers.main.camera();
|
||||||
@ -625,17 +639,15 @@ impl View {
|
|||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
// === Opening Open File or Project Dialog ===
|
// === Project Dialog ===
|
||||||
|
|
||||||
eval_ frp.show_open_dialog (model.show_open_dialog());
|
eval_ frp.show_project_list (model.show_project_list());
|
||||||
project_chosen <- project_list.chosen_entry.constant(());
|
project_chosen <- project_list.grid.entry_selected.constant(());
|
||||||
file_chosen <- file_browser.entry_chosen.constant(());
|
|
||||||
mouse_down <- scene.mouse.frp.down.constant(());
|
mouse_down <- scene.mouse.frp.down.constant(());
|
||||||
clicked_on_bg <- mouse_down.filter(f_!(scene.mouse.target.get().is_background()));
|
clicked_on_bg <- mouse_down.filter(f_!(scene.mouse.target.get().is_background()));
|
||||||
should_be_closed <- any(frp.close_open_dialog,project_chosen,file_chosen,clicked_on_bg);
|
should_be_closed <- any(frp.hide_project_list,project_chosen,clicked_on_bg);
|
||||||
eval_ should_be_closed (model.hide_open_dialog());
|
eval_ should_be_closed (model.hide_project_list());
|
||||||
|
frp.source.project_list_shown <+ bool(&should_be_closed,&frp.show_project_list);
|
||||||
frp.source.open_dialog_shown <+ bool(&should_be_closed,&frp.show_open_dialog);
|
|
||||||
|
|
||||||
|
|
||||||
// === Style toggle ===
|
// === Style toggle ===
|
||||||
@ -675,13 +687,13 @@ impl View {
|
|||||||
|
|
||||||
let documentation = model.searcher.documentation();
|
let documentation = model.searcher.documentation();
|
||||||
searcher_active <- searcher.is_hovered || documentation.frp.is_selected;
|
searcher_active <- searcher.is_hovered || documentation.frp.is_selected;
|
||||||
disable_navigation <- searcher_active || frp.open_dialog_shown;
|
disable_navigation <- searcher_active || frp.project_list_shown;
|
||||||
graph.set_navigator_disabled <+ disable_navigation;
|
graph.set_navigator_disabled <+ disable_navigation;
|
||||||
|
|
||||||
// === Disabling Dropping ===
|
// === Disabling Dropping ===
|
||||||
|
|
||||||
frp.source.drop_files_enabled <+ init.constant(true);
|
frp.source.drop_files_enabled <+ init.constant(true);
|
||||||
frp.source.drop_files_enabled <+ frp.open_dialog_shown.map(|v| !v);
|
frp.source.drop_files_enabled <+ frp.project_list_shown.map(|v| !v);
|
||||||
|
|
||||||
// === Debug Mode ===
|
// === Debug Mode ===
|
||||||
|
|
||||||
@ -712,9 +724,9 @@ impl View {
|
|||||||
&self.model.code_editor
|
&self.model.code_editor
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Open File or Project Dialog
|
/// Open Project Dialog
|
||||||
pub fn open_dialog(&self) -> &OpenDialog {
|
pub fn project_list(&self) -> &ProjectList {
|
||||||
&self.model.open_dialog
|
&self.model.project_list
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Debug Mode Popup
|
/// Debug Mode Popup
|
||||||
@ -751,9 +763,9 @@ impl application::View for View {
|
|||||||
fn default_shortcuts() -> Vec<application::shortcut::Shortcut> {
|
fn default_shortcuts() -> Vec<application::shortcut::Shortcut> {
|
||||||
use shortcut::ActionType::*;
|
use shortcut::ActionType::*;
|
||||||
[
|
[
|
||||||
(Press, "!is_searcher_opened", "cmd o", "show_open_dialog"),
|
(Press, "!is_searcher_opened", "cmd o", "show_project_list"),
|
||||||
(Press, "is_searcher_opened", "escape", "close_searcher"),
|
(Press, "is_searcher_opened", "escape", "close_searcher"),
|
||||||
(Press, "open_dialog_shown", "escape", "close_open_dialog"),
|
(Press, "project_list_shown", "escape", "hide_project_list"),
|
||||||
(Press, "", "cmd alt shift t", "toggle_style"),
|
(Press, "", "cmd alt shift t", "toggle_style"),
|
||||||
(Press, "", "cmd s", "save_project_snapshot"),
|
(Press, "", "cmd s", "save_project_snapshot"),
|
||||||
(Press, "", "cmd r", "restore_project_snapshot"),
|
(Press, "", "cmd r", "restore_project_snapshot"),
|
||||||
|
288
app/gui/view/src/project_list.rs
Normal file
288
app/gui/view/src/project_list.rs
Normal file
@ -0,0 +1,288 @@
|
|||||||
|
//! An interactive list of projects. It used to quickly switch between projects from inside the
|
||||||
|
//! Project View.
|
||||||
|
|
||||||
|
use crate::prelude::*;
|
||||||
|
use ensogl::display::shape::*;
|
||||||
|
|
||||||
|
use enso_frp as frp;
|
||||||
|
use ensogl::application::frp::API;
|
||||||
|
use ensogl::application::Application;
|
||||||
|
use ensogl::data::color;
|
||||||
|
use ensogl::display;
|
||||||
|
use ensogl::display::scene::Layer;
|
||||||
|
use ensogl_component::grid_view;
|
||||||
|
use ensogl_component::list_view;
|
||||||
|
use ensogl_component::shadow;
|
||||||
|
use ensogl_derive_theme::FromTheme;
|
||||||
|
use ensogl_hardcoded_theme::application::project_list as theme;
|
||||||
|
use ensogl_text as text;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// ==============
|
||||||
|
// === Styles ===
|
||||||
|
// ==============
|
||||||
|
|
||||||
|
// === Style ===
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, Default, FromTheme)]
|
||||||
|
#[base_path = "theme"]
|
||||||
|
#[allow(missing_docs)]
|
||||||
|
pub struct Style {
|
||||||
|
width: f32,
|
||||||
|
height: f32,
|
||||||
|
shadow_extent: f32,
|
||||||
|
corners_radius: f32,
|
||||||
|
paddings: f32,
|
||||||
|
#[theme_path = "theme::entry::height"]
|
||||||
|
entry_height: f32,
|
||||||
|
#[theme_path = "theme::bar::height"]
|
||||||
|
bar_height: f32,
|
||||||
|
#[theme_path = "theme::bar::label::padding"]
|
||||||
|
label_padding: f32,
|
||||||
|
#[theme_path = "theme::bar::label::size"]
|
||||||
|
bar_label_size: f32,
|
||||||
|
#[theme_path = "theme::bar::label::color"]
|
||||||
|
bar_label_color: color::Rgba,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// === Entry Style ===
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, Default, FromTheme)]
|
||||||
|
#[base_path = "theme::entry"]
|
||||||
|
#[allow(missing_docs)]
|
||||||
|
pub struct EntryStyle {
|
||||||
|
corners_radius: f32,
|
||||||
|
selection_color: color::Rgba,
|
||||||
|
hover_color: color::Rgba,
|
||||||
|
#[theme_path = "theme::entry::text::padding_left"]
|
||||||
|
text_padding_left: f32,
|
||||||
|
#[theme_path = "theme::entry::text::padding_bottom"]
|
||||||
|
text_padding_bottom: f32,
|
||||||
|
#[theme_path = "theme::entry::text::size"]
|
||||||
|
text_size: f32,
|
||||||
|
#[theme_path = "theme::entry::text::color"]
|
||||||
|
text_color: color::Rgba,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// =============
|
||||||
|
// === Entry ===
|
||||||
|
// =============
|
||||||
|
|
||||||
|
// === Data ===
|
||||||
|
|
||||||
|
/// The model of the list entry. Displays the name of the project.
|
||||||
|
#[derive(Debug, Clone, CloneRef)]
|
||||||
|
struct Data {
|
||||||
|
display_object: display::object::Instance,
|
||||||
|
text: text::Text,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Data {
|
||||||
|
fn new(app: &Application, text_layer: Option<&Layer>) -> Self {
|
||||||
|
let display_object = display::object::Instance::new();
|
||||||
|
let text = text::Text::new(app);
|
||||||
|
display_object.add_child(&text);
|
||||||
|
|
||||||
|
if let Some(text_layer) = text_layer {
|
||||||
|
text.add_to_scene_layer(text_layer);
|
||||||
|
}
|
||||||
|
Self { display_object, text }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_text(&self, text: ImString) {
|
||||||
|
self.text.set_content(text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// === Entry ===
|
||||||
|
|
||||||
|
/// The list entry. Displays the name of the project.
|
||||||
|
#[derive(Debug, Clone, CloneRef)]
|
||||||
|
pub struct Entry {
|
||||||
|
data: Data,
|
||||||
|
frp: grid_view::entry::EntryFrp<Self>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl display::Object for Entry {
|
||||||
|
fn display_object(&self) -> &display::object::Instance {
|
||||||
|
&self.data.display_object
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl grid_view::Entry for Entry {
|
||||||
|
type Model = ImString;
|
||||||
|
type Params = ();
|
||||||
|
|
||||||
|
fn new(app: &Application, text_layer: Option<&Layer>) -> Self {
|
||||||
|
let frp = grid_view::entry::EntryFrp::<Self>::new();
|
||||||
|
let data = Data::new(app, text_layer);
|
||||||
|
|
||||||
|
let network = frp.network();
|
||||||
|
let input = &frp.private().input;
|
||||||
|
let out = &frp.private().output;
|
||||||
|
|
||||||
|
let style_frp = StyleWatchFrp::new(&app.display.default_scene.style_sheet);
|
||||||
|
let style = EntryStyle::from_theme(network, &style_frp);
|
||||||
|
|
||||||
|
frp::extend! { network
|
||||||
|
corners_radius <- style.update.map(|s| s.corners_radius);
|
||||||
|
hover_color <- style.update.map(|s| s.hover_color.into());
|
||||||
|
selection_color <- style.update.map(|s| s.selection_color.into());
|
||||||
|
text_padding_left <- style.update.map(|s| s.text_padding_left);
|
||||||
|
text_padding_bottom <- style.update.map(|s| s.text_padding_bottom);
|
||||||
|
text_size <- style.update.map(|s| s.text_size);
|
||||||
|
text_color <- style.update.map(|s| color::Lcha::from(s.text_color));
|
||||||
|
|
||||||
|
eval input.set_model((text) data.set_text(text.clone_ref()));
|
||||||
|
contour <- input.set_size.map(|s| grid_view::entry::Contour::rectangular(*s));
|
||||||
|
out.contour <+ contour;
|
||||||
|
out.highlight_contour <+ contour.map2(&corners_radius, |c,r| c.with_corners_radius(*r));
|
||||||
|
out.hover_highlight_color <+ hover_color;
|
||||||
|
out.selection_highlight_color <+ selection_color;
|
||||||
|
width <- input.set_size.map(|s| s.x);
|
||||||
|
_eval <- all_with(&width, &text_padding_left, f!((w, p) data.text.set_x(-w / 2.0 + p)));
|
||||||
|
eval text_padding_bottom((p) data.text.set_y(*p));
|
||||||
|
data.text.set_property_default <+ text_size.map(|s| text::Size(*s)).cloned_into_some();
|
||||||
|
data.text.set_property_default <+ text_color.cloned_into_some();
|
||||||
|
}
|
||||||
|
style.init.emit(());
|
||||||
|
Self { data, frp }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn frp(&self) -> &grid_view::entry::EntryFrp<Self> {
|
||||||
|
&self.frp
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// ==================
|
||||||
|
// === Background ===
|
||||||
|
// ==================
|
||||||
|
|
||||||
|
mod background {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
ensogl::shape! {
|
||||||
|
(style:Style) {
|
||||||
|
let sprite_width: Var<Pixels> = "input_size.x".into();
|
||||||
|
let sprite_height: Var<Pixels> = "input_size.y".into();
|
||||||
|
let shadow_extent = style.get_number(theme::shadow_extent);
|
||||||
|
let width = sprite_width - shadow_extent.px() * 2.0;
|
||||||
|
let height = sprite_height - shadow_extent.px() * 2.0;
|
||||||
|
let color = style.get_color(theme::background);
|
||||||
|
let border_size = style.get_number(theme::bar::border_size);
|
||||||
|
let bar_height = style.get_number(theme::bar::height);
|
||||||
|
let corners_radius = style.get_number(theme::corners_radius);
|
||||||
|
let rect = Rect((&width,&height)).corners_radius(corners_radius.px());
|
||||||
|
let shape = rect.fill(color);
|
||||||
|
|
||||||
|
let shadow = shadow::from_shape(rect.into(),style);
|
||||||
|
|
||||||
|
let toolbar_border = Rect((width, border_size.px()))
|
||||||
|
.translate_y(height / 2.0 - bar_height.px())
|
||||||
|
.fill(style.get_color(theme::bar::border_color));
|
||||||
|
|
||||||
|
(shadow + shape + toolbar_border).into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// ===================
|
||||||
|
// === ProjectList ===
|
||||||
|
// ===================
|
||||||
|
|
||||||
|
/// The Project List GUI Component.
|
||||||
|
///
|
||||||
|
/// This is a list of projects in a nice frame with a title.
|
||||||
|
#[derive(Clone, CloneRef, Debug)]
|
||||||
|
pub struct ProjectList {
|
||||||
|
network: frp::Network,
|
||||||
|
display_object: display::object::Instance,
|
||||||
|
background: background::View,
|
||||||
|
caption: text::Text,
|
||||||
|
#[allow(missing_docs)]
|
||||||
|
pub grid: grid_view::scrollable::SelectableGridView<Entry>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ProjectList {
|
||||||
|
/// Create Project List Component.
|
||||||
|
pub fn new(app: &Application) -> Self {
|
||||||
|
let network = frp::Network::new("ProjectList");
|
||||||
|
let display_object = display::object::Instance::new();
|
||||||
|
let background = background::View::new();
|
||||||
|
let caption = app.new_view::<text::Text>();
|
||||||
|
let grid = grid_view::scrollable::SelectableGridView::new(app);
|
||||||
|
display_object.add_child(&background);
|
||||||
|
display_object.add_child(&caption);
|
||||||
|
display_object.add_child(&grid);
|
||||||
|
app.display.default_scene.layers.panel.add(&display_object);
|
||||||
|
caption.set_content("Open Project");
|
||||||
|
caption.add_to_scene_layer(&app.display.default_scene.layers.panel_text);
|
||||||
|
|
||||||
|
ensogl::shapes_order_dependencies! {
|
||||||
|
app.display.default_scene => {
|
||||||
|
background -> list_view::selection;
|
||||||
|
list_view::background -> background;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let style_frp = StyleWatchFrp::new(&app.display.default_scene.style_sheet);
|
||||||
|
let style = Style::from_theme(&network, &style_frp);
|
||||||
|
|
||||||
|
frp::extend! { network
|
||||||
|
init <- source::<()>();
|
||||||
|
width <- style.update.map(|s| s.width);
|
||||||
|
height <- style.update.map(|s| s.height);
|
||||||
|
shadow_extent <- style.update.map(|s| s.shadow_extent);
|
||||||
|
bar_height <- style.update.map(|s| s.bar_height);
|
||||||
|
label_padding <- style.update.map(|s| s.label_padding);
|
||||||
|
label_color <- style.update.map(|s| s.bar_label_color);
|
||||||
|
label_size <- style.update.map(|s| s.bar_label_size);
|
||||||
|
corners_radius <- style.update.map(|s| s.corners_radius);
|
||||||
|
entry_height <- style.update.map(|s| s.entry_height);
|
||||||
|
paddings <- style.update.map(|s| s.paddings);
|
||||||
|
content_size <- all_with3(&width, &height, &init, |w,h,()| Vector2(*w,*h));
|
||||||
|
size <- all_with3(&width, &height, &shadow_extent, |w, h, s|
|
||||||
|
Vector2(w + s * 2.0, h + s * 2.0)
|
||||||
|
);
|
||||||
|
grid_size <- all_with3(&content_size, &bar_height, &paddings,
|
||||||
|
|s, h, p| s - Vector2(0.0, *h) - Vector2(*p * 2.0, *p * 2.0)
|
||||||
|
);
|
||||||
|
grid_width <- grid_size.map(|s| s.x);
|
||||||
|
caption_xy <- all_with3(&width,&height,&label_padding,
|
||||||
|
|w,h,p| Vector2(-*w / 2.0 + *p, *h / 2.0 - p)
|
||||||
|
);
|
||||||
|
eval caption_xy ((xy) caption.set_xy(*xy));
|
||||||
|
eval label_color((color) caption.set_property_default(color));
|
||||||
|
eval label_size((size) caption.set_property_default(text::Size(*size)));
|
||||||
|
_eval <- all_with(&grid_width, &entry_height,
|
||||||
|
f!((w, h) grid.set_entries_size(Vector2(*w, *h)))
|
||||||
|
);
|
||||||
|
eval size((size) background.set_size(*size););
|
||||||
|
eval grid_size((size) grid.scroll_frp().resize(*size));
|
||||||
|
eval corners_radius((r) grid.scroll_frp().set_corner_radius_bottom_left(*r));
|
||||||
|
eval corners_radius((r) grid.scroll_frp().set_corner_radius_bottom_right(*r));
|
||||||
|
grid_x <- grid_width.map(|width| -width / 2.0);
|
||||||
|
grid_y <- all_with3(&content_size, &bar_height, &paddings, |s,h,p| s.y / 2.0 - *h - *p);
|
||||||
|
_eval <- all_with(&grid_x, &grid_y, f!((x, y) grid.set_xy(Vector2(*x, *y))));
|
||||||
|
}
|
||||||
|
style.init.emit(());
|
||||||
|
init.emit(());
|
||||||
|
|
||||||
|
Self { network, display_object, background, caption, grid }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl display::Object for ProjectList {
|
||||||
|
fn display_object(&self) -> &display::object::Instance {
|
||||||
|
&self.display_object
|
||||||
|
}
|
||||||
|
}
|
@ -368,30 +368,32 @@ define_themes! { [light:0, dark:1]
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
file_browser {
|
|
||||||
width = 0.0, 0.0; // Should be updated when file browser will be implemented.
|
|
||||||
height = 421.0, 421.0;
|
|
||||||
}
|
|
||||||
open_dialog {
|
|
||||||
// Should be updated when file browser will be implemented.
|
|
||||||
gap_between_panels = 0.0, 0.0;
|
|
||||||
}
|
|
||||||
project_list {
|
project_list {
|
||||||
width = 202.0 , 202.0;
|
width = 202.0 , 202.0;
|
||||||
padding = 16.0, 16.0;
|
height = 428.0, 428.0;
|
||||||
height = 421.0, 421.0;
|
|
||||||
background = Rgba(0.992,0.996,1.0,1.0), Rgba(0.182,0.188,0.196,1.0);
|
background = Rgba(0.992,0.996,1.0,1.0), Rgba(0.182,0.188,0.196,1.0);
|
||||||
text = widget::list_view::text, widget::list_view::text;
|
shadow_extent = 10.0, 10.0;
|
||||||
text {
|
corners_radius = 16.0, 16.0;
|
||||||
size = 12.0, 12.0;
|
paddings = 4.0, 4.0;
|
||||||
padding = 6.0 , 6.0 ;
|
|
||||||
}
|
|
||||||
bar {
|
bar {
|
||||||
height = 45.0, 45.0;
|
height = 45.0, 45.0;
|
||||||
border_size = 1.0, 1.0;
|
border_size = 1.0, 1.0;
|
||||||
border_color = Rgba(0.808,0.808,0.808,1.0) , Rgba(0.808,0.808,0.808,1.0);
|
border_color = Rgba(0.808,0.808,0.808,1.0) , Rgba(0.808,0.808,0.808,1.0);
|
||||||
label {
|
label {
|
||||||
size = 12.0, 12.0;
|
padding = 16.0, 16.0;
|
||||||
|
size = 12.0, 12.0;
|
||||||
|
color = Rgba(0.439,0.439,0.439,1.0), Rgba(0.439,0.439,0.439,1.0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
entry {
|
||||||
|
height = 25.0, 25.0;
|
||||||
|
corners_radius = application::project_list::corners_radius, application::project_list::corners_radius;
|
||||||
|
selection_color = Rgba::transparent(), Rgba::transparent();
|
||||||
|
hover_color = Rgba(0.906,0.914,0.922,1.0), Rgba(0.906,0.914,0.922,1.0);
|
||||||
|
text {
|
||||||
|
padding_left = 10.0, 10.0;
|
||||||
|
padding_bottom = 7.0, 7.0;
|
||||||
|
size = 12.0, 12.0;
|
||||||
color = Rgba(0.439,0.439,0.439,1.0), Rgba(0.439,0.439,0.439,1.0);
|
color = Rgba(0.439,0.439,0.439,1.0), Rgba(0.439,0.439,0.439,1.0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -39,6 +39,11 @@ impl Contour {
|
|||||||
pub fn rectangular(size: Vector2) -> Self {
|
pub fn rectangular(size: Vector2) -> Self {
|
||||||
Self { size, corners_radius: 0.0 }
|
Self { size, corners_radius: 0.0 }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Adjust the corners radius of the contour.
|
||||||
|
pub fn with_corners_radius(self, radius: f32) -> Self {
|
||||||
|
Self { corners_radius: radius, ..self }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -35,6 +35,18 @@ pub mod js_bindings {
|
|||||||
#[wasm_bindgen(method)]
|
#[wasm_bindgen(method)]
|
||||||
#[wasm_bindgen(js_name = registerSetShadersRustFn)]
|
#[wasm_bindgen(js_name = registerSetShadersRustFn)]
|
||||||
pub fn register_set_shaders_rust_fn(this: &App, closure: &Closure<dyn FnMut(JsValue)>);
|
pub fn register_set_shaders_rust_fn(this: &App, closure: &Closure<dyn FnMut(JsValue)>);
|
||||||
|
|
||||||
|
/// Show a spinner covering the whole viewport.
|
||||||
|
#[allow(unsafe_code)]
|
||||||
|
#[wasm_bindgen(method)]
|
||||||
|
#[wasm_bindgen(js_name = showProgressIndicator)]
|
||||||
|
pub fn show_progress_indicator(this: &App, progress: f32);
|
||||||
|
|
||||||
|
/// Hide a spinner.
|
||||||
|
#[allow(unsafe_code)]
|
||||||
|
#[wasm_bindgen(method)]
|
||||||
|
#[wasm_bindgen(js_name = hideProgressIndicator)]
|
||||||
|
pub fn hide_progress_indicator(this: &App);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -50,6 +62,9 @@ pub mod js_bindings {
|
|||||||
impl App {
|
impl App {
|
||||||
pub fn register_get_shaders_rust_fn(&self, _closure: &Closure<dyn FnMut() -> JsValue>) {}
|
pub fn register_get_shaders_rust_fn(&self, _closure: &Closure<dyn FnMut() -> JsValue>) {}
|
||||||
pub fn register_set_shaders_rust_fn(&self, _closure: &Closure<dyn FnMut(JsValue)>) {}
|
pub fn register_set_shaders_rust_fn(&self, _closure: &Closure<dyn FnMut(JsValue)>) {}
|
||||||
|
|
||||||
|
pub fn show_progress_indicator(&self, _progress: f32) {}
|
||||||
|
pub fn hide_progress_indicator(&self) {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -201,6 +201,7 @@ export class App {
|
|||||||
wasmFunctions: string[] = []
|
wasmFunctions: string[] = []
|
||||||
beforeMainEntryPoints = new Map<string, wasm.BeforeMainEntryPoint>()
|
beforeMainEntryPoints = new Map<string, wasm.BeforeMainEntryPoint>()
|
||||||
mainEntryPoints = new Map<string, wasm.EntryPoint>()
|
mainEntryPoints = new Map<string, wasm.EntryPoint>()
|
||||||
|
progressIndicator: wasm.ProgressIndicator | null = null
|
||||||
initialized = false
|
initialized = false
|
||||||
|
|
||||||
constructor(opts?: {
|
constructor(opts?: {
|
||||||
@ -402,6 +403,25 @@ export class App {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Show a spinner. The displayed progress is constant. */
|
||||||
|
showProgressIndicator(progress: number) {
|
||||||
|
if (this.progressIndicator) {
|
||||||
|
this.hideProgressIndicator()
|
||||||
|
}
|
||||||
|
this.progressIndicator = new wasm.ProgressIndicator(this.config)
|
||||||
|
this.progressIndicator.set(progress)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Hide the progress indicator. */
|
||||||
|
hideProgressIndicator() {
|
||||||
|
if (this.progressIndicator) {
|
||||||
|
// Setting the progress to 100% is necessary to allow animation to finish.
|
||||||
|
this.progressIndicator.set(1)
|
||||||
|
this.progressIndicator.destroy()
|
||||||
|
this.progressIndicator = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** Run both before main entry points and main entry point. */
|
/** Run both before main entry points and main entry point. */
|
||||||
async runEntryPoints() {
|
async runEntryPoints() {
|
||||||
const entryPointName = this.config.groups.startup.options.entry.value
|
const entryPointName = this.config.groups.startup.options.entry.value
|
||||||
|
@ -17,7 +17,7 @@ const ghostColor = '#00000020'
|
|||||||
const topLayerIndex = '1000'
|
const topLayerIndex = '1000'
|
||||||
|
|
||||||
/** Visual representation of the loader. */
|
/** Visual representation of the loader. */
|
||||||
class ProgressIndicator {
|
export class ProgressIndicator {
|
||||||
dom: HTMLDivElement
|
dom: HTMLDivElement
|
||||||
track: HTMLElement
|
track: HTMLElement
|
||||||
indicator: HTMLElement
|
indicator: HTMLElement
|
||||||
|
Loading…
Reference in New Issue
Block a user