Skip to content
This repository has been archived by the owner on Dec 28, 2021. It is now read-only.

Fullscreen Visualization #1355

Merged
merged 17 commits into from
Mar 30, 2021
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
will be lost. In this build we added notification in statusbar to signalize
that the connection was lost and IDE must be restarted. In future IDE will try
to automatically reconnect.
- [Visualization can be extended to the whole screen][1355] by selecting
the node and pressing space twice. To quit this view, press space again.

<br/>![Bug Fixes](/docs/assets/tags/bug_fixes.svg)

Expand Down Expand Up @@ -62,6 +64,7 @@ you can find their release notes
[1064]: https://github.com/enso-org/ide/pull/1064
[1316]: https://github.com/enso-org/ide/pull/1316
[1318]: https://github.com/enso-org/ide/pull/1318
[1355]: https://github.com/enso-org/ide/pull/1355

<br/>

Expand Down
3 changes: 3 additions & 0 deletions src/js/lib/content/src/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@
.visualization {
z-index: 2;
border-radius: 14px;
/* visualizations are put into the fullscreen-vis layer which has pointer-events set to none, so we need to
override it */
pointer-events: auto;
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is the visualization class attached to fullscreen visualizations only? If so, it has a bad name.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no, but is attached to the div element we move to the fullscreen-vis. I will change the comment to "may be put"


#crash-banner {
Expand Down
59 changes: 54 additions & 5 deletions src/rust/ensogl/lib/core/src/display/object/class.rs
Original file line number Diff line number Diff line change
Expand Up @@ -342,22 +342,26 @@ impl<Host> Model<Host> {

/// Hide all removed children and show this display object if it was attached to a new parent.
fn update_visibility(&self, host:&Host, parent_scene_layers:&[LayerId]) {
self.take_removed_children_and_hide_orphans(host);
self.take_removed_children_and_update_their_visibility(host);
let parent_changed = self.dirty.parent.check();
if parent_changed && !self.is_orphan() {
self.set_vis_true(host,parent_scene_layers)
}
}

fn take_removed_children_and_hide_orphans(&self, host:&Host) {
fn take_removed_children_and_update_their_visibility(&self, host:&Host) {
if self.dirty.removed_children.check_all() {
debug!(self.logger, "Updating removed children.", || {
for child in self.dirty.removed_children.take().into_iter() {
if let Some(child) = child.upgrade() {
if child.is_orphan() {
if !child.has_visible_parent() {
child.set_vis_false(host);
Comment on lines +359 to 360
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldnt this change call "set_vis_false" on a child that was already hidden sometimes? For example, if a hidden child was moved to a visible parent, which just got hidden the next frame? If this could happen, this is blocker from merging. I'd love to chat why it never happens (if it never happens). We should add tests about it.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it may happen. But if the visible parent got hidden, then it will call set_vis_false, and thus the moved child will be also hidden.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So this is bad. set_vis_false sohuld be guaranteed to be called only once and never be called on a thing already hidden. Otherwise it can break some rendering assumptions. The same with showing things.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

but set_vis_false checks if the instance is visible. I will add a test to make sure that on_hide callback is not called redundantly.

child.take_removed_children_and_hide_orphans(host);
}
// Even if the child is visible at this point, it does not mean that it
// should be visible after the entire update. Therefore, we must ensure that
// "removed children" lists in its subtree will be managed.
// See also test `visibility_test3`.
child.take_removed_children_and_update_their_visibility(host);
}
}
})
Expand Down Expand Up @@ -606,7 +610,7 @@ impl<Host> Instance<Host> {
}

/// Add this object to the provided scene layer and remove it from all other layers. Do not use
// /// this method explicitly. Use layers' methods instead.
/// this method explicitly. Use layers' methods instead.
pub(crate) fn add_to_scene_layer_exclusive(&self, layer:LayerId) {
self.dirty.scene_layer.set();
*self.scene_layers.borrow_mut() = vec![layer];
Expand Down Expand Up @@ -681,6 +685,11 @@ impl<Host> Instance<Host> {
fn parent_index(&self) -> Option<usize> {
self.parent_bind.borrow().as_ref().map(|t| t.index)
}

fn has_visible_parent(&self) -> bool {
let parent = self.parent_bind.borrow().as_ref().and_then(|b| b.parent.upgrade());
parent.map_or(false, |parent| parent.is_visible())
}
}


Expand Down Expand Up @@ -1238,6 +1247,46 @@ mod tests {
assert_eq!(node2.is_visible(),true);
}

#[test]
fn visibility_test3() {
let node1 = Instance::<()>::new(Logger::new("node1"));
let node2 = Instance::<()>::new(Logger::new("node2"));
let node3 = Instance::<()>::new(Logger::new("node3"));
node1.force_set_visibility(true);
node1.add_child(&node2);
node2.add_child(&node3);
node1.update(&());

node3.unset_parent();
node3.add_child(&node2);
node1.update(&());
assert_eq!(node2.is_visible(),false);
assert_eq!(node3.is_visible(),false);
}

#[test]
fn visibility_test4() {
let node1 = Instance::<()>::new(Logger::new("node1"));
let node2 = Instance::<()>::new(Logger::new("node2"));
let node3 = Instance::<()>::new(Logger::new("node3"));
let node4 = Instance::<()>::new(Logger::new("node4"));
node1.force_set_visibility(true);
node1.add_child(&node2);
node2.add_child(&node3);
node1.update(&());

node2.unset_parent();
node1.add_child(&node2);
node2.add_child(&node3);
node1.update(&());
node1.add_child(&node4);
node4.add_child(&node3);
node1.update(&());
assert_eq!(node2.is_visible(),true);
assert_eq!(node3.is_visible(),true);
}


#[test]
fn deep_hierarchy_test() {

Expand Down
33 changes: 23 additions & 10 deletions src/rust/ensogl/lib/core/src/display/scene.rs
Original file line number Diff line number Diff line change
Expand Up @@ -458,9 +458,12 @@ impl Dom {
#[derive(Clone,CloneRef,Debug)]
pub struct DomLayers {
/// Back DOM scene layer.
pub back: DomScene,
pub back : DomScene,
/// Back DOM with fullscreen visualization. Kept separately from `back`, because use different
/// Camera
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this has several problems:

  1. It's not gramatically correct ('use')
  2. does not have dot at the end.
  3. States that this is a "back DOM", which is not specific enough, as this is a back DOM scene.
  4. Does not explain what a "different camera" is and why do we use a different camera here. It should be explained here soduring future refactoring it would be clear what it is about.

pub fullscreen_vis : DomScene,
/// Front DOM scene layer.
pub front: DomScene,
pub front : DomScene,
/// The WebGL scene layer.
pub canvas : web_sys::HtmlCanvasElement,

Expand All @@ -469,25 +472,30 @@ pub struct DomLayers {
impl DomLayers {
/// Constructor.
pub fn new(logger:&Logger, dom:&web_sys::HtmlDivElement) -> Self {
let canvas = web::create_canvas();
let front = DomScene::new(logger);
let back = DomScene::new(logger);
let canvas = web::create_canvas();
let front = DomScene::new(logger);
let fullscreen_vis = DomScene::new(logger);
let back = DomScene::new(logger);
canvas.set_style_or_warn("height" , "100vh" , &logger);
canvas.set_style_or_warn("width" , "100vw" , &logger);
canvas.set_style_or_warn("display" , "block" , &logger);
// Position must not be "static" to have z-index working.
canvas.set_style_or_warn("position" , "absolute", &logger);
canvas.set_style_or_warn("z-index" , "1" , &logger);
canvas.set_style_or_warn("z-index" , "2" , &logger);
canvas.set_style_or_warn("pointer-events", "none" , &logger);
front.dom.set_class_name("front");
front.dom.set_style_or_warn("z-index", "1", &logger);
back.dom.set_class_name("back");
back.dom.set_style_or_warn("pointer-events", "auto", &logger);
back.dom.set_style_or_warn("z-index" , "0" , &logger);
fullscreen_vis.dom.set_class_name("fullscreen_vis");
// fullscreen_vis.dom.set_style_or_warn("pointer-events", "auto", &logger);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

commented code

fullscreen_vis.dom.set_style_or_warn("z-index" , "1" , &logger);
dom.append_or_panic(&canvas);
dom.append_or_panic(&front.dom);
dom.append_or_panic(&back.dom);
Self {front,canvas,back}
dom.append_or_panic(&fullscreen_vis.dom);
Self {front,canvas,back,fullscreen_vis}
}
}

Expand Down Expand Up @@ -614,7 +622,7 @@ pub struct HardcodedLayers {

pub viz_fullscreen : Layer,
pub breadcrumbs : Layer,
layers : Layers,
layers : Layers,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You left spaces on the end of the line, which is strange, as IDEs should trim it on save

}

impl Deref for HardcodedLayers {
Expand Down Expand Up @@ -854,14 +862,19 @@ impl SceneData {
fn update_camera(&self, scene:&Scene) {
// Updating camera for DOM layers. Please note that DOM layers cannot use multi-camera
// setups now, so we are using here the main camera only.
let camera = self.camera();
let changed = camera.update(scene);
let camera = self.camera();
let fullscreen_vis_camera = self.layers.viz_fullscreen.camera();
let changed = camera.update(scene);
if changed {
self.frp.camera_changed_source.emit(());
self.symbols.set_camera(&camera);
self.dom.layers.front.update_view_projection(&camera);
self.dom.layers.back.update_view_projection(&camera);
}
let fs_vis_camera_changed = fullscreen_vis_camera.update(scene);
if fs_vis_camera_changed {
self.dom.layers.fullscreen_vis.update_view_projection(&fullscreen_vis_camera);
}

// Updating all other cameras (the main camera was already updated, so it will be skipped).
for layer in &*self.layers.all() {
Expand Down
3 changes: 3 additions & 0 deletions src/rust/ensogl/lib/core/src/display/scene/dom.rs
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,9 @@ impl DomScene {
pub fn manage(&self, object:&DomSymbol) {
let dom = object.dom();
let data = &self.data;
if object.is_visible() {
self.view_projection_dom.append_or_panic(&dom);
}
object.display_object().set_on_hide(f_!(dom.remove()));
object.display_object().set_on_show(f__!([data,dom] {
data.view_projection_dom.append_or_panic(&dom)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -689,8 +689,16 @@ class Histogram extends Visualization {
* Sets size of the main parent DOM object.
*/
setSize(size) {
console.warn('setting size', size)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Such things should not be left in a code

this.dom.setAttributeNS(null, 'width', size[0])
this.dom.setAttributeNS(null, 'height', size[1])

while (this.dom.firstChild) {
this.dom.removeChild(this.dom.lastChild)
}
this.initCanvas()
this.initLabels()
this.initHistogram()
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,13 +51,69 @@ class ScatterPlot extends Visualization {
while (this.dom.firstChild) {
this.dom.removeChild(this.dom.lastChild)
}

const divElem = this.createDivElem(this.canvasWidth(), this.canvasHeight())
this.dom.appendChild(divElem)

let parsedData = this.parseData(data)
this.updateState(parsedData)
this.drawScatterplot()
}

canvasWidth() {
return this.dom.getAttributeNS(null, 'width')
}

canvasHeight() {
let height = this.dom.getAttributeNS(null, 'height')
return height - buttonsHeight
}

updateState(parsedData) {
this.axis = parsedData.axis || {
x: { scale: linear_scale },
y: { scale: linear_scale },
}
this.focus = parsedData.focus
this.points = parsedData.points || { labels: 'invisible' }
this.dataPoints = this.extractValues(parsedData)
this.updateBox()
}

updateBox() {
this.margin = this.getMargins(this.axis)
this.box_width = this.canvasWidth() - this.margin.left - this.margin.right
this.box_height = this.canvasHeight() - this.margin.top - this.margin.bottom
}

extractValues(data) {
/// Note this is a workaround for #1006, we allow raw arrays and JSON objects to be consumed.
if (Array.isArray(data)) {
return this.arrayAsValues(data)
}
return data.data || {}
}

/**
* Return a array of values as valid scatterplot input.
* Uses the values of the array as y-coordinate and the index of the value as its x-coordinate.
*/
arrayAsValues(dataArray) {
return dataArray.map((y, ix) => {
return { x: ix, y }
})
}

parseData(data) {
let parsedData
/// Note this is a workaround for #1006, we allow raw arrays and JSON objects to be consumed.
if (typeof data === 'string' || data instanceof String) {
parsedData = JSON.parse(data)
} else {
parsedData = data
}
return parsedData
}

drawScatterplot() {
const divElem = this.createDivElem(this.canvasWidth(), this.canvasHeight())
this.dom.appendChild(divElem)
let svg = d3
.select(divElem)
.append('svg')
Expand Down Expand Up @@ -117,58 +173,6 @@ class ScatterPlot extends Visualization {
)
}

canvasWidth() {
return this.dom.getAttributeNS(null, 'width')
}

canvasHeight() {
let height = this.dom.getAttributeNS(null, 'height')
return height - buttonsHeight
}

updateState(parsedData) {
this.axis = parsedData.axis || {
x: { scale: linear_scale },
y: { scale: linear_scale },
}
this.focus = parsedData.focus
this.points = parsedData.points || { labels: 'invisible' }
this.dataPoints = this.extractValues(parsedData)

this.margin = this.getMargins(this.axis)
this.box_width = this.canvasWidth() - this.margin.left - this.margin.right
this.box_height = this.canvasHeight() - this.margin.top - this.margin.bottom
}

extractValues(data) {
/// Note this is a workaround for #1006, we allow raw arrays and JSON objects to be consumed.
if (Array.isArray(data)) {
return this.arrayAsValues(data)
}
return data.data || {}
}

/**
* Return a array of values as valid scatterplot input.
* Uses the values of the array as y-coordinate and the index of the value as its x-coordinate.
*/
arrayAsValues(dataArray) {
return dataArray.map((y, ix) => {
return { x: ix, y }
})
}

parseData(data) {
let parsedData
/// Note this is a workaround for #1006, we allow raw arrays and JSON objects to be consumed.
if (typeof data === 'string' || data instanceof String) {
parsedData = JSON.parse(data)
} else {
parsedData = data
}
return parsedData
}

/**
* Adds panning and zooming functionality to the visualization.
*/
Expand Down Expand Up @@ -728,8 +732,14 @@ class ScatterPlot extends Visualization {
* Sets size of this DOM object.
*/
setSize(size) {
while (this.dom.firstChild) {
this.dom.removeChild(this.dom.lastChild)
}

this.dom.setAttributeNS(null, 'width', size[0])
this.dom.setAttributeNS(null, 'height', size[1])
this.updateBox()
this.drawScatterplot()
}
}

Expand Down
Loading