Skip to content

Several enhancements #553

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Apr 12, 2019
Merged
Show file tree
Hide file tree
Changes from all 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: 2 additions & 1 deletion docs/about.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@ About

.. toctree::
:maxdepth: 2

releasenotes
contributing
freeze
overview
motivation
eventsystembackground
40 changes: 40 additions & 0 deletions docs/freeze.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
Freezing Flexx apps
-------------------

Flexx needs special care when freezing, because it needs access to the Python
source code in order to compile it to JavaScript.

First, create a script that represents your application entry point. It
is important that this script does not define any new Flexx widgets. It
should look something like this:

.. code-block:: py

# Install hook so we we can import modules from source when frozen.
from flexx.util import freeze
freeze.install()

# Run your app as usual
from flexx import flx
from my_module import MyWidget
app = flx.App(MyWidget)
app.launch("firefox-app")
flx.run()


Next, use ``PyInstaller`` as usual to create an app directory. Consider
using ``--exclude-module numpy`` (PyInstaller thinks that Flexx needs Numpy, but this not the case).

Next, copy the source code of the modules that define Flexx widgets. If you
run PyInstaller from a script (using ``PyInstaller.__main__.run()``) then you
can combine it.

.. code-block:: py

from flexx.util import freeze

freeze.copy_module("flexx", app_dir) # always
freeze.copy_module("flexxamples", app_dir) # used something from here?
freeze.copy_module("my_module", app_dir)

That should be it!
10 changes: 6 additions & 4 deletions flexx/app/_funcs.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,8 @@ def __init__(self, session):
self.enable()

def enable(self):
from IPython import get_ipython
get_ipython = None
exec("from IPython import get_ipython") # noqa - dont trigger e.g. PyInstaller
ip = get_ipython()
ip.events.register('pre_execute', self.capture)
ip.events.register('post_execute', self.release)
Expand All @@ -109,7 +110,8 @@ def release(self):
self._session._ws = self._real_ws
self._real_ws = None
if self._commands:
from IPython.display import display, Javascript
display = Javascript = None
exec("from IPython.display import display, Javascript") # noqa - dont trigger e.g. PyInstaller
lines = []
lines.append('var bb64 = flexx.require("bb64");')
lines.append('function cmd(c) {'
Expand Down Expand Up @@ -139,8 +141,8 @@ def init_notebook():
# Note: not using IPython Comm objects yet, since they seem rather
# undocumented and I could not get them to work when I tried for a bit.
# This means though, that flexx in the notebook only works on localhost.

from IPython.display import display, clear_output, HTML
display = clear_output = HTML = None
exec("from IPython.display import display, clear_output, HTML") # noqa - dont trigger e.g. PyInstaller
# from .. import ui # noqa - make ui assets available

# Make default log level warning instead of "info" to avoid spamming
Expand Down
12 changes: 11 additions & 1 deletion flexx/ui/_widget.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,8 @@ class Widget(app.JsComponent):
equal to ``outernode``. For the ``Widget`` class, this is simply a
`<div> <https://developer.mozilla.org/docs/Web/HTML/Element/div>`_
element. If you don't understand what this is about, don't worry;
you won't need it unless you are creating your own low-level widgets :)
you won't need it unless you are creating your own low-level widgets.
See ``_create_dom()`` for details.

When implementing your own widget class, the class attribute
``DEFAULT_MIN_SIZE`` can be set to specify a sensible minimum size.
Expand Down Expand Up @@ -402,6 +403,15 @@ def _create_dom(self):
values. These attributes must remain unchanged throughout the
lifetime of a widget. This method can be overloaded in
subclasses.

Most widgets have the same value for ``node`` and ``outernode``.
However, in some cases it helps to distinguish between the
semantic "actual node" and a wrapper. E.g. Flexx uses it to
properly layout the ``CanvasWidget`` and ``TreeItem``.
Internally, Flexx uses the ``node`` attribute for tab-index, and
binding to mouse/touch/scroll/key events. If your ``outernode``
already semantically represents your widget, you should probably
just use that.
"""
return create_element('div')

Expand Down
1 change: 1 addition & 0 deletions flexx/ui/layouts/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,4 @@
from ._tabs import TabLayout
from ._pinboard import PinboardLayout
from ._form import FormLayout
from ._grid import GridLayout
195 changes: 24 additions & 171 deletions flexx/ui/layouts/_form.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,201 +18,54 @@ def init(self):

"""

from pscript import window, undefined
from pscript import window

from ... import event
from . import Layout
from .. import create_element


class BaseTableLayout(Layout):
""" Abstract base class for layouts that use an HTML table.

Layouts that use this approach don't have good performance when
resizing. This is not so much a problem when it is used as a leaf
layout, but it's not recommended to embed such layouts in each-other.
"""

CSS = """

/* Clear any styling on this table (rendered_html is an IPython thing) */
.flx-BaseTableLayout, .flx-BaseTableLayout td, .flx-BaseTableLayout tr,
.rendered_html .flx-BaseTableLayout {
border: 0px;
padding: initial;
margin: initial;
background: initial;
}

/* Behave well inside hbox/vbox,
we assume no layouts to be nested inside a table layout */
.flx-box.flx-horizontal > .flx-BaseTableLayout {
width: auto;
}
.flx-box.flx-vertical > .flx-BaseTableLayout {
height: auto;
}

td.flx-vflex, td.flx-hflex {
padding: 2px;
}

/* In flexed cells, occupy the full space */
td.flx-vflex > .flx-Widget {
height: 100%;
}
td.flx-hflex > .flx-Widget {
width: 100%;
}
"""


def _apply_table_layout(self):
table = self.node
AUTOFLEX = 729 # magic number unlikely to occur in practice

# Get table dimensions
nrows = len(table.children)
ncols = 0
for i in range(len(table.children)):
row = table.children[i]
ncols = max(ncols, len(row.children))
if ncols == 0 and nrows == 0:
return

# Collect flexes
vflexes = []
hflexes = []
for i in range(nrows):
row = table.children[i]
for j in range(ncols):
col = row.children[j]
if (col is undefined) or (len(col.children) == 0):
continue
vflexes[i] = max(vflexes[i] or 0, col.children[0].vflex or 0)
hflexes[j] = max(hflexes[j] or 0, col.children[0].hflex or 0)

# What is the cumulative "flex-value"?
cum_vflex = vflexes.reduce(lambda pv, cv: pv + cv, 0)
cum_hflex = hflexes.reduce(lambda pv, cv: pv + cv, 0)

# If no flexes are given; assign each equal
if (cum_vflex == 0):
for i in range(len(vflexes)):
vflexes[i] = AUTOFLEX
cum_vflex = len(vflexes) * AUTOFLEX
if (cum_hflex == 0):
for i in range(len(hflexes)):
hflexes[i] = AUTOFLEX
cum_hflex = len(hflexes) * AUTOFLEX

# Assign css class and height/weight to cells
for i in range(nrows):
row = table.children[i]
row.vflex = vflexes[i] or 0 # Store for use during resizing
for j in range(ncols):
col = row.children[j]
if (col is undefined) or (col.children.length == 0):
continue
self._apply_cell_layout(row, col, vflexes[i], hflexes[j],
cum_vflex, cum_hflex)

@event.reaction('size')
def _adapt_to_size_change(self, *events):
""" This function adapts the height (in percent) of the flexible rows
of a layout. This is needed because the percent-height applies to the
total height of the table. This function is called whenever the
table resizes, and adjusts the percent-height, taking the available
remaining table height into account. This is not necesary for the
width, since percent-width in colums *does* apply to available width.
"""
table = self.node # or event.target
#print('heigh changed', event.heightChanged, event.owner.__id)

if events[-1].new_value[1] != events[0].old_value[1]:

# Set one flex row to max, so that non-flex rows have their
# minimum size. The table can already have been stretched
# a bit, causing the total row-height in % to not be
# sufficient from keeping the non-flex rows from growing.
for i in range(len(table.children)):
row = table.children[i]
if (row.vflex > 0):
row.style.height = '100%'
break

# Get remaining height: subtract height of each non-flex row
remainingHeight = table.clientHeight
cum_vflex = 0
for i in range(len(table.children)):
row = table.children[i]
cum_vflex += row.vflex
if (row.vflex == 0) and (row.children.length > 0):
remainingHeight -= row.children[0].clientHeight

# Apply height % for each flex row
remainingPercentage = 100 * remainingHeight / table.clientHeight
for i in range(len(table.children)):
row = table.children[i]
if row.vflex > 0:
row.style.height = round(row.vflex /cum_vflex *
remainingPercentage) + 1 + '%'

def _apply_cell_layout(self, row, col, vflex, hflex, cum_vflex, cum_hflex):
raise NotImplementedError()



class FormLayout(BaseTableLayout):
class FormLayout(Layout):
""" A layout widget that vertically alligns its child widgets in a form.
A label is placed to the left of each widget (based on the widget's title).

The ``node`` of this widget is a
`<table> <https://developer.mozilla.org/docs/Web/HTML/Element/table>`_.
(This may be changed to use a CSS layout instead.)
`<div> <https://developer.mozilla.org/docs/Web/HTML/Element/div>`_,
which lays out it's child widgets and their labels using
`CSS grid <https://css-tricks.com/snippets/css/complete-guide-grid/>`_.
"""

CSS = """
.flx-FormLayout .flx-title {
.flx-FormLayout {
display: grid;
grid-template-columns: auto 1fr;
justify-content: stretch;
align-content: stretch;
justify-items: stretch;
align-items: center;

}
.flx-FormLayout > .flx-title {
text-align: right;
padding-right: 5px;
}
"""

def _create_dom(self):
return window.document.createElement('table')
return window.document.createElement('div')

def _render_dom(self):
rows = []
row_templates = []
for widget in self.children:
row = create_element('tr', {},
create_element('td', {'class': 'flx-title'}, widget.title),
create_element('td', {}, [widget.outernode]),
)
widget.outernode.hflex = 1
widget.outernode.vflex = widget.flex[1]
rows.append(row)
event.loop.call_soon(self._apply_table_layout)
rows.extend([
create_element('div', {'class': 'flx-title'}, widget.title),
widget.outernode,
])
flex = widget.flex[1]
row_templates.append(flex + "fr" if flex > 0 else "auto")
self.node.style['grid-template-rows'] = " ".join(row_templates)
return rows

def _apply_cell_layout(self, row, col, vflex, hflex, cum_vflex, cum_hflex):
AUTOFLEX = 729
className = ''
if (vflex == AUTOFLEX) or (vflex == 0):
row.style.height = 'auto'
className += ''
else:
row.style.height = vflex * 100 / cum_vflex + '%'
className += 'flx-vflex'
className += ' '
if (hflex == 0):
col.style.width = 'auto'
className += ''
else:
col.style.width = '100%'
className += 'flx-hflex'
col.className = className

def _query_min_max_size(self):
""" Overload to also take child limits into account.
"""
Expand Down
Loading