diff --git a/src/foam/u2/view/ScrollTableView.js b/src/foam/u2/view/ScrollTableView.js index 2a8eee8cadb..f3e9fbf1209 100644 --- a/src/foam/u2/view/ScrollTableView.js +++ b/src/foam/u2/view/ScrollTableView.js @@ -8,6 +8,7 @@ 'foam.graphics.ScrollCView', 'foam.dao.FnSink', 'foam.mlang.sink.Count', + 'foam.input.Scroll', ], properties: [ @@ -15,11 +16,27 @@ class: 'foam.dao.DAOProperty', name: 'data' }, + { + class: 'Int', + name: 'height', + value: 200, + }, + { + class: 'Int', + name: 'headerHeight', + value: 28, + }, + { + class: 'Int', + name: 'rowHeight', + value: 28, + }, { class: 'Int', name: 'limit', - value: 30, - // TODO make this a funciton of the height. + expression: function(height, rowHeight, headerHeight) { + return Math.round((height - headerHeight) / rowHeight); + }, }, { class: 'Int', @@ -29,58 +46,82 @@ class: 'foam.dao.DAOProperty', name: 'scrolledDao', expression: function(data, limit, skip) { - return data.limit(limit).skip(skip); + return data ? data.limit(limit).skip(skip) : foam.dao.NullDAO.create(); }, }, { name: 'scrollView', factory: function() { - var self = this; - return this.ScrollCView.create({ + var s = this.ScrollCView.create({ value$: this.skip$, - extent$: this.limit$, - height: 600, // TODO use window height. + height$: this.height$, width: 40, handleSize: 40, - // TODO wire up mouse wheel - // TODO clicking away from scroller should deselect it. }); + s.extent$.follow(this.limit$); + return s; }, }, { name: 'tableView', factory: function() { - return this.TableView.create({data$: this.scrolledDao$}); + var v = this.TableView.create({data$: this.scrolledDao$}); + v.attrs({border: 1}); + return v; }, }, + { + name: 'scroll', + factory: function() { return this.Scroll.create({element: this.tableView}) }, + }, ], listeners: [ { - // TODO Avoid onDaoUpdate approaches. name: 'onDaoUpdate', isFramed: true, code: function() { var self = this; this.data$proxy.select(this.Count.create()).then(function(s) { + var isAtBottom = self.scrollView.size >= self.skip + self.limit; self.scrollView.size = s.value; + self.skip = isAtBottom ? + s.value - self.limit : + Math.min(self.skip, s.value - self.limit); }) }, }, + { + name: 'onScroll', + code: function(_, _, touch) { + this.skip += (Math.round(touch.deltaY) || (touch.deltaY > 0 ? 1 : -1)); + touch.claimed = true; + } + } ], methods: [ function init() { + this.onDetach(this.scroll.scroll.sub(this.onScroll)); this.onDetach(this.data$proxy.pipe(this.FnSink.create({fn:this.onDaoUpdate}))); }, function initE() { // TODO probably shouldn't be using a table. this.start('table').style({'width':'100%'}). start('tr'). - start('td').style({'width':'100%'}).add(this.tableView).end(). + start('td').style({'vertical-align': 'top', 'width':'100%'}).add(this.tableView).end(). start('td').add(this.scrollView).end(). end(). end(); } - ] + ], + + actions: [ + function scrollToBottom() { + var self = this; + this.data$proxy.select(this.Count.create()).then(function(s) { + self.skip = s.value - self.limit; + }) + }, + ], }); diff --git a/src/lib/input.js b/src/lib/input.js index 023564a5bfd..5bc984cd1e8 100644 --- a/src/lib/input.js +++ b/src/lib/input.js @@ -130,6 +130,91 @@ foam.CLASS({ }); +foam.CLASS({ + package: 'foam.input', + name: 'ScrollEvent', + + properties: [ + { + class: 'Float', + name: 'deltaX' + }, + { + class: 'Float', + name: 'deltaY' + }, + { + class: 'Boolean', + name: 'claimed', + value: false + } + ] +}); + + +foam.CLASS({ + package: 'foam.input', + name: 'Scroll', + + requires: [ + 'foam.input.Touch', + 'foam.input.ScrollEvent', + ], + + topics: [ + 'scroll' + ], + + properties: [ + { + name: 'lastTouch', + }, + { + name: 'element', + postSet: function(old, e) { + if ( old ) { + old.removeEventListener('wheel', this.onWheel); + } + e.addEventListener('wheel', this.onWheel); + } + } + ], + + methods: [ + function init() { + this.onDetach( + this.Touch.create({element$: this.element$}).touch.sub(this.onTouch)); + }, + ], + + listeners: [ + function onWheel(e) { + var scrollEvent = this.ScrollEvent.create({ + deltaX: e.deltaX, + deltaY: e.deltaY, + }); + this.scroll.pub(scrollEvent); + if (scrollEvent.claimed) { + e.preventDefault(); + } + }, + function onTouch(_, _, touch) { + var lastTouch = touch.clone(); + var self = this; + this.onDetach(touch.sub(function() { + var scrollEvent = self.ScrollEvent.create({ + deltaX: lastTouch.x - touch.x, + deltaY: lastTouch.y - touch.y, + }); + lastTouch = touch.clone(); + self.scroll.pub(scrollEvent); + touch.claimed = scrollEvent.claimed; + })); + }, + ] +}); + + foam.CLASS({ package: 'foam.input', name: 'Touch',