diff --git a/gui/Cargo.toml b/gui/Cargo.toml index 4aa9b0e331..3022d6797b 100644 --- a/gui/Cargo.toml +++ b/gui/Cargo.toml @@ -14,6 +14,7 @@ members = [ "lib/ide/ast/macros", "lib/ide/file-manager", "lib/ide/file-manager/mock-server", + "lib/ide/", "lib/ide/json-rpc", "lib/ide/parser", "lib/ide/utils", diff --git a/gui/app/src/index.js b/gui/app/src/index.js index 6afef95627..a53183ebbe 100644 --- a/gui/app/src/index.js +++ b/gui/app/src/index.js @@ -73,7 +73,7 @@ async function download_content(cfg) { // ==================== /// The name of the main scene in the WASM binary. -let main_scene_name = 'shapes' +let main_scene_name = 'ide' /// Prefix name of each scene defined in the WASM binary. let wasm_fn_pfx = "run_example_" diff --git a/gui/lib/core/msdf-sys/src/lib.rs b/gui/lib/core/msdf-sys/src/lib.rs index a37fce2149..27d612ca6e 100644 --- a/gui/lib/core/msdf-sys/src/lib.rs +++ b/gui/lib/core/msdf-sys/src/lib.rs @@ -27,7 +27,7 @@ use wasm_bindgen::prelude::Closure; /// Add initialization callback /// -/// The callback passed as argument will be called once the msdfgen libirary +/// The callback passed as argument will be called once the msdfgen library /// will be initialized. pub fn run_once_initialized(callback:F) where F : 'static + FnOnce() { diff --git a/gui/lib/core/src/animation/physics/inertia.rs b/gui/lib/core/src/animation/physics/inertia.rs index 8fca62d3f7..37a9ab46d1 100644 --- a/gui/lib/core/src/animation/physics/inertia.rs +++ b/gui/lib/core/src/animation/physics/inertia.rs @@ -242,7 +242,7 @@ impl PhysicsProperties { impl PhysicsProperties { /// Safe accessor to modify `KinematicsProperties`. - pub fn mod_kinematics(&mut self, f:F) { + pub fn modify_kinematics(&mut self, f:F) { let mut kinematics = self.kinematics(); f(&mut kinematics); self.set_kinematics(kinematics); @@ -254,7 +254,7 @@ impl PhysicsProperties { } /// Safe accessor to modify `SpringProperties`. - pub fn mod_spring(&mut self, f:F) { + pub fn modify_spring(&mut self, f:F) { let mut spring = self.spring(); f(&mut spring); self.set_spring(spring); @@ -266,7 +266,7 @@ impl PhysicsProperties { } /// Safe accessor to modify `DragProperties`. - pub fn mod_drag(&mut self, f:F) { + pub fn modify_drag(&mut self, f:F) { let mut drag = self.drag(); f(&mut drag); self.set_drag(drag); @@ -278,7 +278,7 @@ impl PhysicsProperties { } /// Safe accessor to modify `SimulationThresholds`. - pub fn mod_thresholds(&mut self, f:F) { + pub fn modify_thresholds(&mut self, f:F) { let mut thresholds = self.thresholds(); f(&mut thresholds); self.set_thresholds(thresholds); @@ -362,7 +362,7 @@ impl PhysicsSimulator { let fixed_point = properties.spring().fixed_point; let thresholds = properties.thresholds(); - properties.mod_kinematics(|kinematics| { + properties.modify_kinematics(|kinematics| { let speed = kinematics.velocity.magnitude(); let position = kinematics.position(); let distance = (position - fixed_point).magnitude(); @@ -398,7 +398,7 @@ fn simulate(properties:&mut PhysicsProperties, delta_ms:f64) -> Vector3 { let spring = properties.spring(); let drag = properties.drag(); let mut net_force = zero(); - properties.mod_kinematics(|mut kinematics| { + properties.modify_kinematics(|mut kinematics| { net_force += spring.force(&kinematics); net_force += drag.force(&kinematics); let delta_seconds = delta_ms / 1000.0; diff --git a/gui/lib/core/src/display/camera/camera2d.rs b/gui/lib/core/src/display/camera/camera2d.rs index 892d380e85..f4a829630a 100644 --- a/gui/lib/core/src/display/camera/camera2d.rs +++ b/gui/lib/core/src/display/camera/camera2d.rs @@ -7,7 +7,9 @@ use crate::data::dirty; use crate::display::layout::types::*; use crate::display::object::DisplayObjectData; use crate::data::dirty::traits::*; -use crate::control::callback::{XCallbackRegistry1, CallbackHandle, XCallbackMut1Fn}; +use crate::control::callback::XCallbackRegistry1; +use crate::control::callback::XCallbackMut1Fn; +use crate::control::callback::CallbackHandle; use nalgebra::Vector2; use nalgebra::Vector3; diff --git a/gui/lib/core/src/display/navigation/navigator.rs b/gui/lib/core/src/display/navigation/navigator.rs index e1894eb90c..3fd1009f8b 100644 --- a/gui/lib/core/src/display/navigation/navigator.rs +++ b/gui/lib/core/src/display/navigation/navigator.rs @@ -93,18 +93,18 @@ impl Navigator { let y = pan.movement.y * scale; let z = 0.0; - properties.mod_spring(|spring| { spring.fixed_point += Vector3::new(x, y, z); }); + properties.modify_spring(|spring| { spring.fixed_point += Vector3::new(x, y, z); }); }); let transform = camera.transform(); let resize_callback = camera.add_screen_update_callback( enclose!((mut properties,transform) move |_:&Vector2| { let position = transform.position(); - properties.mod_kinematics(|kinematics| { + properties.modify_kinematics(|kinematics| { kinematics.set_position(position); kinematics.set_velocity(Vector3::new(0.0, 0.0, 0.0)); }); - properties.mod_spring(|spring| spring.fixed_point = position); + properties.modify_spring(|spring| spring.fixed_point = position); }) ); @@ -126,7 +126,7 @@ impl Navigator { let min_zoom = camera.clipping().near + min_zoom; position.z = clamp(position.z, min_zoom, max_zoom); - properties.mod_spring(|spring| spring.fixed_point = position); + properties.modify_spring(|spring| spring.fixed_point = position); }; (resize_callback, NavigatorEvents::new(dom, panning_callback, zoom_callback, zoom_speed)) } diff --git a/gui/lib/core/src/display/shape/text/text_field.rs b/gui/lib/core/src/display/shape/text/text_field.rs index 7fa1ea114d..d858d82f7f 100644 --- a/gui/lib/core/src/display/shape/text/text_field.rs +++ b/gui/lib/core/src/display/shape/text/text_field.rs @@ -42,6 +42,7 @@ pub struct TextFieldProperties { /// Text size being a line height in pixels. pub text_size: f32, /// Base color of displayed text. + //TODO: base_color should use definitions in core/data/color pub base_color: Vector4, /// Size of this component. pub size: Vector2, @@ -83,6 +84,16 @@ shared! { TextField self.display_object.set_position(position); } + /// Get position of this TextField. + pub fn position(&self) -> Vector3 { + self.display_object.position() + } + + /// Get size. + pub fn size(&self) -> Vector2 { + self.properties.size + } + /// Scroll text by given offset in pixels. pub fn scroll(&mut self, offset:Vector2) { let position_change = -Vector3::new(offset.x,offset.y,0.0); diff --git a/gui/lib/core/tests/physics_simulator.rs b/gui/lib/core/tests/physics_simulator.rs index db72e8d838..bff34ec147 100644 --- a/gui/lib/core/tests/physics_simulator.rs +++ b/gui/lib/core/tests/physics_simulator.rs @@ -77,7 +77,7 @@ mod tests { let y = 240.0 * random() as f32; let z = 0.0; let position = Vector3::new(x, y, z); - properties.mod_spring(|spring| spring.fixed_point = position); + properties.modify_spring(|spring| spring.fixed_point = position); target.mod_position(|t| *t = position); }); diff --git a/gui/lib/gui/Cargo.toml b/gui/lib/gui/Cargo.toml index 9bf26869e7..6148784dfc 100644 --- a/gui/lib/gui/Cargo.toml +++ b/gui/lib/gui/Cargo.toml @@ -7,12 +7,12 @@ edition = "2018" [lib] crate-type = ["cdylib"] - [dependencies] basegl = { version = "0.1.0" , path = "../core" } basegl-core-msdf-sys = { version = "0.1.0" , path = "../core/msdf-sys" } basegl-system-web = { version = "0.1.0" , path = "../system/web" } -enso-frp = { version = "0.1.0" , path = "../frp" } +enso-frp = { version = "0.1.0" , path = "../frp" } +ide = { version = "0.1.0" , path = "../ide" } enso-prelude = { version = "0.1.0" , path = "../prelude" } wasm-bindgen = { version = "0.2.58" , features = ["nightly"] } diff --git a/gui/lib/gui/src/css3d_system.rs b/gui/lib/gui/src/css3d_system.rs index a5d2d957c9..9708521cc7 100644 --- a/gui/lib/gui/src/css3d_system.rs +++ b/gui/lib/gui/src/css3d_system.rs @@ -10,9 +10,9 @@ use basegl::display::object::DisplayObjectOps; use basegl::display::symbol::geometry::Sprite; use basegl::display::symbol::geometry::SpriteSystem; use basegl::display::world::*; +use basegl::display::navigation::navigator::Navigator; use basegl::prelude::*; use basegl::animation::animator::fixed_step::FixedStepAnimator; -use basegl::display::navigation::navigator::Navigator; use nalgebra::Vector2; use nalgebra::Vector3; diff --git a/gui/lib/gui/src/ide.rs b/gui/lib/gui/src/ide.rs new file mode 100644 index 0000000000..822d9eb5b6 --- /dev/null +++ b/gui/lib/gui/src/ide.rs @@ -0,0 +1,25 @@ +//! This module defines the entrypoint function for IDE. + +use wasm_bindgen::prelude::*; + +use basegl::system::web; +use ide::run_ide; + +/// IDE startup function. +#[wasm_bindgen] +#[allow(dead_code)] +pub fn run_example_ide() { + web::forward_panic_hook_to_console(); + web::set_stdout(); + + // FIXME: This code is temporary. It's used to remove the loader UI. + basegl_core_msdf_sys::run_once_initialized(|| { + web::get_element_by_id("loader").map(|t| { + t.parent_node().map(|p| { + p.remove_child(&t).unwrap() + }) + }).ok(); + + run_ide() + }); +} diff --git a/gui/lib/gui/src/lib.rs b/gui/lib/gui/src/lib.rs index 9d2dece78f..34c72fdeb5 100644 --- a/gui/lib/gui/src/lib.rs +++ b/gui/lib/gui/src/lib.rs @@ -25,5 +25,6 @@ pub mod sprite_system; pub mod text_field; pub mod text_typing; pub mod css3d_system; +pub mod ide; use enso_prelude as prelude; diff --git a/gui/lib/ide/Cargo.toml b/gui/lib/ide/Cargo.toml new file mode 100644 index 0000000000..3c8397fed6 --- /dev/null +++ b/gui/lib/ide/Cargo.toml @@ -0,0 +1,46 @@ +[package] +name = "ide" +version = "0.1.0" +authors = ["Enso Team "] +edition = "2018" + +[lib] +crate-type = ["cdylib", "rlib"] + +[dependencies] +basegl = { version = "0.1.0" , path = "../core" } +enso-prelude = { version = "0.1.0" , path = "../prelude" } +file-manager-client = { version = "0.1.0" , path = "file-manager" } +json-rpc = { version = "0.1.0" , path = "json-rpc" } +utils = { version = "0.1.0" , path = "utils" } +basegl-core-msdf-sys = { version = "0.1.0" , path = "../core/msdf-sys" } +shapely = { version = "0.1.0" , path = "../shapely/impl" } + +console_error_panic_hook = { version = "0.1.6" } +futures = { version = "0.3.1" } +nalgebra = { version = "0.19.0" } +js-sys = { version = "0.3.35" } +serde = { version = "1.0", features = ["derive"] } +serde_json = { version = "1.0" } +uuid = { version = "0.8", features = ["serde", "v5"] } +wasm-bindgen = { version = "0.2.58" } +wasm-bindgen-test = { version = "0.3.8" } + +[target.'cfg(not(target_arch = "wasm32"))'.dependencies] +websocket = "0.23.0" + +[dependencies.web-sys] +version = "0.3.22" +features = [ + 'Blob', + 'console', + 'CloseEvent', + 'Document', + 'Element', + "ErrorEvent", + "MessageEvent", + 'HtmlElement', + 'Node', + 'WebSocket', + 'Window', +] diff --git a/gui/lib/ide/json-rpc/src/handler.rs b/gui/lib/ide/json-rpc/src/handler.rs index baa8463f82..d32341380e 100644 --- a/gui/lib/ide/json-rpc/src/handler.rs +++ b/gui/lib/ide/json-rpc/src/handler.rs @@ -321,6 +321,7 @@ impl Handler { match event { TransportEvent::TextMessage(msg) => self.process_incoming_message(msg), + TransportEvent::Opened => {} TransportEvent::Closed => { // Dropping all ongoing calls will cancel their futures. self.clear_ongoing_requests(); diff --git a/gui/lib/ide/json-rpc/src/transport.rs b/gui/lib/ide/json-rpc/src/transport.rs index 29b70d98ad..d6370d2204 100644 --- a/gui/lib/ide/json-rpc/src/transport.rs +++ b/gui/lib/ide/json-rpc/src/transport.rs @@ -25,6 +25,8 @@ pub trait Transport : Debug { pub enum TransportEvent { /// A text message with has been received. TextMessage(String), + /// A socket has been opened. + Opened, /// A socket has been closed by the peer. Closed, } diff --git a/gui/lib/ide/src/entry_point.rs b/gui/lib/ide/src/entry_point.rs new file mode 100644 index 0000000000..e69de29bb2 diff --git a/gui/lib/ide/src/lib.rs b/gui/lib/ide/src/lib.rs new file mode 100644 index 0000000000..a0cec93cfa --- /dev/null +++ b/gui/lib/ide/src/lib.rs @@ -0,0 +1,34 @@ +//! Main library crate for IDE. It includes implementation of +//! controllers, view logic and code that wraps them all together. + +#![warn(missing_docs)] +#![warn(trivial_casts)] +#![warn(trivial_numeric_casts)] +#![warn(unused_import_braces)] +#![warn(unused_qualifications)] +#![warn(unsafe_code)] +#![warn(missing_copy_implementations)] +#![warn(missing_debug_implementations)] + +#[allow(unused)] +pub mod todo; +pub mod view; + +#[allow(missing_docs)] +/// Common types that should be visible across the whole IDE crate. +pub mod prelude { + pub use enso_prelude::*; + + pub use futures::Future; + pub use futures::FutureExt; + pub use futures::Stream; + pub use futures::StreamExt; + pub use futures::task::LocalSpawnExt; +} + +use view::project::ProjectView; + +/// This function is the IDE entry point responsible for setting up all views and controllers. +pub fn run_ide() { + ProjectView::new().forget(); +} \ No newline at end of file diff --git a/gui/lib/ide/src/todo/executor.rs b/gui/lib/ide/src/todo/executor.rs new file mode 100644 index 0000000000..7f54485931 --- /dev/null +++ b/gui/lib/ide/src/todo/executor.rs @@ -0,0 +1,105 @@ +//! TODO [mwu] This module is still provisional work in progress. Currently +//! provided only for tests purposes, needs to be completed and +//! properly reviewed. +//! +//! Module providing the executor-related types and functions. + +#![allow(missing_docs)] // TODO [mwu] remove once module is done + +use crate::prelude::*; + +use futures::task::LocalSpawnExt; +use futures::task::LocalSpawn; +use futures::task::LocalFutureObj; +use futures::task::SpawnError; +use futures::executor::LocalPool; +use futures::executor::LocalSpawner; + +use basegl::control::callback::CallbackHandle; +use basegl::control::EventLoop; + +// TODO [mwu] If anything, likely thread local variable should be preferred. +static mut CURRENT_SPAWNER: Option> = None; + +// TODO [mwu] consider whether it is the "current" spawner or rather +// "the global" spawner. Is it available from outside the async +// context? Do we allow using more than one executor in the IDE? +#[allow(unsafe_code)] +pub fn set_global_spawner(spawner: impl LocalSpawn + 'static) { + unsafe { + CURRENT_SPAWNER = Some(Box::new(spawner)); + } +} + +#[allow(unsafe_code)] +pub fn unset_global_spawner() { + unsafe { + CURRENT_SPAWNER = None; + } +} + +#[allow(unsafe_code)] +pub fn current_spawner() -> &'static mut dyn LocalSpawn { + unsafe { + CURRENT_SPAWNER.as_mut().expect("no global executor has been provided") + } +} + +/// Spawn a task scheduled within a current executor. +/// Panics, if called when there is no active asynchronous execution. +pub fn spawn_task(f:impl Future + 'static) { + current_spawner().spawn_local(f).ok(); +} + + + +#[derive(Debug)] +pub struct JsExecutor { + _executor : Rc>, + _event_loop : EventLoop, + spawner : LocalSpawner, + _cb_handle : CallbackHandle, +} + +impl JsExecutor { + pub fn new(_event_loop:EventLoop) -> JsExecutor { + let _executor = LocalPool::default(); + let spawner = _executor.spawner(); + let _executor = Rc::new(RefCell::new(_executor)); + let _cb_handle = JsExecutor::schedule_execution(_event_loop.clone(), _executor.clone()); + JsExecutor {_executor,_event_loop,spawner,_cb_handle} + } + + pub fn schedule_execution + (event_loop:EventLoop, executor:Rc>) -> CallbackHandle { + event_loop.add_callback(move |_| { + // Safe, because this is the only place borrowing executor and loop + // callback shall never be re-entrant. + let mut executor = executor.borrow_mut(); + set_global_spawner(executor.spawner()); + executor.run_until_stalled(); + unset_global_spawner(); + }) + } + + pub fn spawn + (&self, f:impl Future + 'static) + -> Result<(), SpawnError> { + self.spawner.spawn_local(f) + } + + pub fn add_callback + (&mut self, callback:F) -> CallbackHandle { + self._event_loop.add_callback(callback) + } +} + +impl LocalSpawn for JsExecutor { + fn spawn_local_obj(&self, future: LocalFutureObj<'static, ()>) -> Result<(), SpawnError> { + self.spawner.spawn_local_obj(future) + } + + fn status_local(&self) -> Result<(), SpawnError> { + self.spawner.status_local() + } +} diff --git a/gui/lib/ide/src/todo/mod.rs b/gui/lib/ide/src/todo/mod.rs new file mode 100644 index 0000000000..d12d46627e --- /dev/null +++ b/gui/lib/ide/src/todo/mod.rs @@ -0,0 +1,3 @@ +//! Provisional modules being work in progress. + +pub mod executor; diff --git a/gui/lib/ide/src/view.rs b/gui/lib/ide/src/view.rs new file mode 100644 index 0000000000..c1f7a1d77c --- /dev/null +++ b/gui/lib/ide/src/view.rs @@ -0,0 +1,15 @@ +//! A module containing view components. + +#![warn(unsafe_code)] +#![warn(missing_copy_implementations)] +#![warn(missing_debug_implementations)] +#![warn(missing_docs)] +#![warn(trivial_casts)] +#![warn(trivial_numeric_casts)] +#![warn(unused_import_braces)] +#![warn(unused_qualifications)] + +pub mod temporary_panel; +pub mod project; +pub mod layout; +pub mod text_editor; diff --git a/gui/lib/ide/src/view/layout.rs b/gui/lib/ide/src/view/layout.rs new file mode 100644 index 0000000000..b3a5b1136d --- /dev/null +++ b/gui/lib/ide/src/view/layout.rs @@ -0,0 +1,178 @@ +//! This module contains implementation of ViewLayout with a single TextEditor temporarily +//! occupying half bottom of the screen as the default layout. + +use wasm_bindgen::prelude::*; +use basegl::prelude::*; + +use crate::view::temporary_panel::TemporaryPadding; +use crate::view::text_editor::TextEditor; +use crate::view::temporary_panel::TemporaryPanel; + +use basegl::system::web::*; +use basegl::display::world::World; +use js_sys::Function; +use nalgebra::zero; +use nalgebra::Vector2; +use std::rc::Rc; +use std::cell::RefCell; +use wasm_bindgen::JsCast; +use web_sys::KeyboardEvent; +use web_sys::HtmlElement; + + + +// ================== +// === LayoutMode === +// ================== +//TODO: LayoutMode is a temporary enumeration, it will be replaced by proper Panel impl. + +/// Defines the element's layout mode. It can fully occupy the screen or only half of it. +#[derive(Clone,Copy,Debug)] +pub enum LayoutMode { + #[allow(missing_docs)] + Full, + #[allow(missing_docs)] + Half +} + +impl Default for LayoutMode { + fn default() -> Self { + Self::Half + } +} + + + +// ======================== +// === KeyboardListener === +// ======================== + +type KeyboardClosure = Closure; + +#[derive(Debug)] +struct KeyboardListener { + callback : KeyboardClosure, + element : HtmlElement, + event_type : String +} + +impl KeyboardListener { + fn new(element:&HtmlElement, event_type:String, callback:KeyboardClosure) -> Self { + let element = element.clone(); + Self {callback,element,event_type} + } +} + +impl Drop for KeyboardListener { + fn drop(&mut self) { + let callback : &Function = self.callback.as_ref().unchecked_ref(); + self.element.remove_event_listener_with_callback(&self.event_type, callback).ok(); + } +} + + + +// ================== +// === ViewLayout === +// ================== + +shared! { ViewLayout + +/// Initial implementation of ViewLayout with a single TextEditor. Pressing ctrl+f toggles +/// fullscreen mode. +#[derive(Debug)] +pub struct ViewLayoutData { + text_editor : TextEditor, + key_listener : Option, + layout_mode : LayoutMode, + size : Vector2 +} + +impl { + /// Switches LayoutMode between Half and Full. + pub fn switch_layout_mode(&mut self) { + if let LayoutMode::Half = self.layout_mode { + self.set_layout_mode(LayoutMode::Full) + } else { + self.set_layout_mode(LayoutMode::Half) + } + } + + /// Sets ViewLayout size. + pub fn set_size(&mut self, size:Vector2) { + self.size = size; + self.recalculate_layout(); + } +}} + + +// === Private Methods === + +impl ViewLayoutData { + fn set_layout_mode(&mut self, layout_mode:LayoutMode) { + self.layout_mode = layout_mode; + self.recalculate_layout(); + } + + fn recalculate_layout(&mut self) { + let size = self.size; + let (position,size) = match self.layout_mode { + LayoutMode::Full => { + let position = Vector2::new(0.0,size.y); + (position,size) + }, + LayoutMode::Half => { + let position = Vector2::new(0.0,size.y / 2.0); + let size = Vector2::new(size.x,size.y / 2.0); + (position,size) + } + }; + let padding = TemporaryPadding { + left : 10.0, + top : 0.0, + right : 10.0, + bottom : 0.0 + }; + self.text_editor.set_padding(padding); + self.text_editor.set_size(size); + self.text_editor.set_position(position); + self.text_editor.update(); + } +} + +impl ViewLayout { + /// Creates a new ViewLayout with a single TextEditor. + pub fn default(world:&World) -> Self { + let text_editor = TextEditor::new(&world); + let key_listener = None; + let layout_mode = default(); + let size = zero(); + let data = ViewLayoutData {text_editor,key_listener,layout_mode,size}; + let rc = Rc::new(RefCell::new(data)); + Self {rc}.init(world) + } + + fn init_keyboard(self) -> Self { + let view_layout = self.clone(); + let closure = move |event:KeyboardEvent| { + const F_KEY : u32 = 70; + if event.ctrl_key() && event.key_code() == F_KEY { + view_layout.switch_layout_mode(); + event.prevent_default(); + } + }; + let closure : Box = Box::new(closure); + let callback = Closure::wrap(closure); + let body = document().unwrap().body().unwrap(); + let key_listener = KeyboardListener::new(&body, "keydown".into(), callback); + self.rc.borrow_mut().key_listener = Some(key_listener); + self + } + + fn init(self, world:&World) -> Self { + let screen = world.scene().camera().screen(); + let size = Vector2::new(screen.width,screen.height); + self.set_size(size); + self.init_keyboard() + } +} diff --git a/gui/lib/ide/src/view/project.rs b/gui/lib/ide/src/view/project.rs new file mode 100644 index 0000000000..8659c32c91 --- /dev/null +++ b/gui/lib/ide/src/view/project.rs @@ -0,0 +1,81 @@ +//! This module contains ProjectView, the main view, responsible for managing TextEditor and +//! GraphEditor. + +use crate::prelude::*; + +use super::layout::ViewLayout; + +use basegl::display::world::WorldData; +use basegl::display::world::World; +use basegl::system::web; +use basegl::control::callback::CallbackHandle; + +use nalgebra::Vector2; +use shapely::shared; + + + +// =================== +// === ProjectView === +// =================== + +shared! { ProjectView + + /// ProjectView is the main view of the project, holding instances of TextEditor and + /// GraphEditor. + #[derive(Debug)] + pub struct ProjectViewData { + world : World, + layout : ViewLayout, + resize_callback: Option + } + + impl { + /// Set view size. + pub fn set_size(&mut self, size:Vector2) { + self.layout.set_size(size); + } + } +} + +impl Default for ProjectViewData { + fn default() -> Self { + let world = WorldData::new(&web::body()); + let layout = ViewLayout::default(&world); + let resize_callback = None; + ProjectViewData{world,layout,resize_callback} + } +} + +impl ProjectView { + /// Create new ProjectView. + pub fn new() -> Self { + let data = default(); + Self{rc:data}.init() + } + + fn init(self) -> Self { + let scene = self.with_borrowed(|data| data.world.scene()); + let weak = self.downgrade(); + let resize_callback = scene.camera().add_screen_update_callback( + move |size:&Vector2| { + if let Some(this) = weak.upgrade() { + this.set_size(*size) + } + } + ); + self.with_borrowed(move |data| data.resize_callback = Some(resize_callback)); + self + } + + /// Forgets ProjectView, so it won't get dropped when it goes out of scope. + pub fn forget(self) { + std::mem::forget(self) + } +} + +impl Default for ProjectView { + fn default() -> Self { + Self::new() + } +} diff --git a/gui/lib/ide/src/view/temporary_panel.rs b/gui/lib/ide/src/view/temporary_panel.rs new file mode 100644 index 0000000000..b353ea3b1c --- /dev/null +++ b/gui/lib/ide/src/view/temporary_panel.rs @@ -0,0 +1,52 @@ +//! This module contains a temporary definition of Panel. There is a plan to implement proper +//! panels in the future. + +use nalgebra::Vector2; + + + +// =============== +// === Padding === +// =============== + +/// A struct containing the padding values. +/// This code is temporary and should be used with cautious. +#[derive(Clone,Copy,Debug,Default)] +pub struct TemporaryPadding { + #[allow(missing_docs)] + pub left : f32, + #[allow(missing_docs)] + pub top : f32, + #[allow(missing_docs)] + pub right : f32, + #[allow(missing_docs)] + pub bottom : f32 +} + + + +// ============= +// === Panel === +// ============= + +/// A trait defining a panel interface. +/// This code is temporary and should be used with cautious. +pub trait TemporaryPanel { + /// Sets padding. + fn set_padding(&mut self, padding: TemporaryPadding); + + /// Gets padding. + fn padding(&self) -> TemporaryPadding; + + /// Sets size. + fn set_size(&mut self, size:Vector2); + + /// Gets size. + fn size(&self) -> Vector2; + + /// Sets position. + fn set_position(&mut self, position:Vector2); + + /// Gets position. + fn position(&self) -> Vector2; +} diff --git a/gui/lib/ide/src/view/text_editor.rs b/gui/lib/ide/src/view/text_editor.rs new file mode 100644 index 0000000000..e8d434097f --- /dev/null +++ b/gui/lib/ide/src/view/text_editor.rs @@ -0,0 +1,98 @@ +//! This module contains TextEditor, an UiComponent to edit Enso Modules or Text Files. + +use crate::prelude::*; + +use super::temporary_panel::TemporaryPanel; +use super::temporary_panel::TemporaryPadding; + +use basegl::display::object::DisplayObjectOps; +use basegl::display::shape::text::glyph::font::FontRegistry; +use basegl::display::shape::text::text_field::TextField; +use basegl::display::shape::text::text_field::TextFieldProperties; +use basegl::display::world::*; + +use nalgebra::Vector2; +use nalgebra::zero; + + +// ================== +// === TextEditor === +// ================== + +/// TextEditor allows us to edit text files or Enso Modules. Extensible code highlighting is +/// planned to be implemented for it. +#[derive(Clone,Debug)] +pub struct TextEditor { + text_field : TextField, + padding : TemporaryPadding, + position : Vector2, + size : Vector2 +} + +impl TextEditor { + /// Creates a new TextEditor. + pub fn new(world:&World) -> Self { + let scene = world.scene(); + let camera = scene.camera(); + let screen = camera.screen(); + let mut fonts = FontRegistry::new(); + let font = fonts.get_or_load_embedded_font("DejaVuSansMono").unwrap(); + let padding = default(); + let position = zero(); + let size = Vector2::new(screen.width, screen.height); + let black = Vector4::new(0.0,0.0,0.0,1.0); + let base_color = black; + let text_size = 16.0; + let properties = TextFieldProperties {font,text_size,base_color,size}; + let text_field = TextField::new(&world,properties); + world.add_child(&text_field); + + Self {text_field,padding,position,size}.initialize() + } + + fn initialize(self) -> Self { + self.update(); + self + } + + /// Updates the underlying display object. + pub fn update(&self) { + let padding = self.padding; + let position = self.position; + let position = Vector3::new(position.x + padding.left, position.y + padding.bottom, 0.0); + self.text_field.set_position(position); + // TODO: set text field size once the property change will be supported. + // let padding = Vector2::new(padding.left + padding.right, padding.top + padding.bottom); + // self.text_field.set_size(self.dimensions - padding); + self.text_field.update(); + } +} + +impl TemporaryPanel for TextEditor { + fn set_padding(&mut self, padding: TemporaryPadding) { + self.padding = padding; + } + + fn padding(&self) -> TemporaryPadding { + self.padding + } + + fn set_size(&mut self, size:Vector2) { + self.size = size; + self.update(); + } + + fn size(&self) -> Vector2 { + self.text_field.size() + } + + fn set_position(&mut self, position:Vector2) { + self.position = position; + self.update(); + } + + fn position(&self) -> Vector2 { + let position = self.text_field.position(); + Vector2::new(position.x, position.y) + } +} diff --git a/gui/lib/shapely/impl/src/shared.rs b/gui/lib/shapely/impl/src/shared.rs index 1f16ec692c..ac7d88fc90 100644 --- a/gui/lib/shapely/impl/src/shared.rs +++ b/gui/lib/shapely/impl/src/shared.rs @@ -212,14 +212,6 @@ macro_rules! shared_struct { $(#[$($meta)*])* pub struct [] <$($params)*> { weak: Weak>> } - impl<$($params)*> $name <$($params)*> { - /// Downgrade the reference to weak ref. - pub fn downgrade(&self) -> [] <$($params)*> { - let weak = Rc::downgrade(&self.rc); - [] {weak} - } - } - impl<$($params)*> Clone for [] <$($params)*> { fn clone(&self) -> Self { let weak = self.weak.clone(); @@ -236,6 +228,26 @@ macro_rules! shared_struct { self.weak.upgrade().map(|rc| $name {rc}) } } + + impl<$($params)*> $name <$($params)*> { + /// Downgrade the reference to weak ref. + pub fn downgrade(&self) -> [] <$($params)*> { + let weak = Rc::downgrade(&self.rc); + [] {weak} + } + + /// Call operation with borrowed data. Should be use in implementation of wrapper + /// only. + fn with_borrowed(&self, operation:F) -> R + where F : FnOnce(&mut $name_mut<$($params)*>) -> R { + operation(&mut self.rc.borrow_mut()) + } + + /// Check if the shared pointer points to the same struct as `other`. + pub fn identity_equals(&self, other:&Self) -> bool { + Rc::ptr_eq(&self.rc,&other.rc) + } + } } }; }