omz:forum

    • Register
    • Login
    • Search
    • Recent
    • Popular

    Welcome!

    This is the community forum for my apps Pythonista and Editorial.

    For individual support questions, you can also send an email. If you have a very short question or just want to say hello — I'm @olemoritz on Twitter.


    Anchors with scripter

    Pythonista
    3
    3
    1470
    Loading More Posts
    • Oldest to Newest
    • Newest to Oldest
    • Most Votes
    Reply
    • Reply as topic
    Log in to reply
    This topic has been deleted. Only users with topic management privileges can see it.
    • mikael
      mikael last edited by mikael

      Here's my summer project, a re-imagining of the anchor module. It is now built on top of scripter and the ui.View update method. As a result, it is much more flexible, has a lower barrier for entry, and does not crash.

      First, a picture of a UI built mostly using the new anchors:

      Portrait version

      A sharp eye may notice how parts of the UI dynamically adjust when the device is rotated to landscape, shown in this second picture:

      Landscape version

      To cover the main anchor features, please refer to this picture with handy numbering:

      With markers

      1. at is the basic workhorse. Applied to views, you can set layout constraints that hold when the UI otherwise changes. In the example, to fix the left edge of the blue view fix_left to the vertical bar:

        at(fix_left).left = at(vertical_bar).right
        

        Now, if the vertical bar moves for some reason, our fixed view goes right along.

      2. You might have noticed the small gap between the view and the vertical bar. This is a Apple Standard gap of 8 points, and it is included between anchored views by default. You can add any modifiers to an anchor to change this gap. If you specifically want the views to be flush against each other, there is a constant for that, as demonstrated by our second example view, flex:

        at(flex).left = at(vertical_bar).right + At.TIGHT
        

        There is an option of changing the gap between views globally, to 0 if you want, by setting the At.gap class variable at the top of your program.

      3. If we fix the other end of the view as well, the view width is adjusted as needed. The example demonstrates fixing the other end to the edge of the containing view, which looks very similar to the previous examples:

        at(flex).right = at(containing_view).right
        

        The full set of at attributes is:

        • Edges: left, right, top, bottom
        • Center: center, center_x, center_y
        • Size: width, height, size
        • Position: position
        • Position and size: frame, bounds
        • "Exotics": heading, fit_size, fit_width, fit_height
      4. As an experiment in what can be done beyond the previous Apple's UI constraint implementation, there is an anchor that will keep a view pointed to the center of another view:

        at(pointer).heading = at(target).center
        

        Implementation assumes that the pointer is initially pointed to 0, or right. If your graphic is actually initially pointed to, say, down, you can make the heading constraint work by telling it how much the initial angle needs to be adjusted (in radians): at(pointer).heading_adjustment = -math.pi/2 would mean a 90 degree turn counterclockwise.

      5. Generalizing the basic anchor idea, I included an attr function that can be used to "anchor" any attribute of any object. In the example, we anchor the text attribute of a nearby label to always show the heading of the pointer. Because the heading is a number and the label expects a string, there is an option of including a translation function like this:

        attr(heading_label).text = at(pointer).heading + str
        

        Actually, since the plain radians look a bit ugly, a little bit more complicated conversion is needed:

        attr(heading_label).text = at(pointer).heading + (
            lambda heading: f'{int(math.degrees(heading))%360:03}°'
        )
        
      6. Docking or placing a view in some corner or some other position relative to its superview is so common, that there is a dock convenience function specifically for that purpose. For example, to attach the top_center_view to the top center of the container view:

        dock(container).top_center(top_center_view)
        

        Full set of superview docking functions is:

        • all, bottom, top, right, left
        • top_left, top_right, bottom_left, bottom_right
        • sides (left and right), vertical (top and bottom)
        • top_center, bottom_center, left_center, right_center
        • center
      7. Often, it is convenient to set the same anchor for several views at once. align function does exactly this, in this example aligning all the labels in the labels array:

        align(container).center_y(*labels)
        

        align attributes match all the at attributes for which the concept of setting several anchors at once may make sense:
        left, right, top, bottom, center, center_x, center_y, width, height, position, size, frame, bounds, heading.

      8. Filling an area with similarly-sized containers can be done with the fill function. In the example we create, in the content_area superview, 4 areas in 2 columns:

        fill(content_area, 2).from_top(at_area, dock_area, pointer_area, align_area)
        

        Default value of 1 creates fills a single column or row. Filling can be started from any of the major directions: from_top, from_bottom, from_left, from_right.

      9. flow function is another layout helper that lets you add a number of views which placed side by side, then wrapped at the end of the row, mimicking the basic text flow when you start from the top left like in the example:

        flow(button_area).from_top_left(*buttons)
        

        The full set of flow functions support starting from different corners and flowing either horizontally or vertically:

        • Horizontal: from_top_left, from_bottom_left, from_top_right, from_bottom_right
        • Vertical: from_left_down, from_right_down, from_left_up, from_right_up
      10. For sizing a superview according to its contents, you can use the fit_size, fit_width or fit_height anchor attributes. In our example we make the button container resize according to how much space the buttons need, with the content area above stretching accordingly:

        at(button_area).height = at(button_area).fit_height
        at(content_area).bottom = at(button_area).top - At.TIGHT
        

        If you use this, it is good to consider how volatile your anchors make the views within the container. For example, you cannot have a vertical flow in a view that automatically resizes its height.

      11. By default, all anchors respect the safe areas of your device, e.g. the top and bottom on an iPhone in a portrait orientation. You can turn this default off by setting the At.safe class variable to False.

      So, with that brief look at the available features, I would ask you to:

      • share your opinion on the API and usability in general

      • share any ideas you might have for other useful anchors or convenience functions

      • share your view on whether comparison-based anchors would be useful, and for what (i.e. something like at(view).width >= at(other_view).width)

      • check out the anchor_demo.py code for the pictures above, to see the hairy bits, or

      • give it a try in something you are building:

        pip install pythonista-scripter to get the latest scripter version with the anchor support
        from scripter.anchor import * to get going

        (If you do, remember that you do not have to build the whole UI with this. Just use it for something simple, like keeping a menu button in the right place.)

      Sorry for the long post, and thank you for sticking with it.

      1 Reply Last reply Reply Quote 2
      • JonB
        JonB last edited by

        This looks amazing.

        1 Reply Last reply Reply Quote 0
        • cvp
          cvp last edited by

          Not tested, but whaaaaaaaa, as usual

          1 Reply Last reply Reply Quote 0
          • First post
            Last post
          Powered by NodeBB Forums | Contributors