Forum Archive

Dynamic views and Images in the ui module

philg

I've been trying to create an interface prompts for a Camera Roll pic (photos module) and displays it, which is fairly simple using ImageView() and ui.Image.from_data(photos.pick_image(raw_data=True)) with ui.CONTENT_SCALE_ASPECT_FIT. The image will properly fit the bounds of the screen and adapt as you rotate the device.

My problem has been that I want to create touch events, but ImageView() can't handle those, so I assume I have to create a custom View on top of it, however, since I want the touch events to be available solely within the image displayed below, I must make this "touch view" the same size as the "image view".

Trying ImageView.image.size will give me the original dimensions, while I want the dimensions after the ui.content_mode is applied. My best bet right now is to run a function that calculates the outcome from the content_mode based on the ui.get_screen_size(), however, this one always delivers the dimensions in portrait mode, meanwhile I don't know a way to get the device current orientation.

The question is: how to make a View with the same width and height of the image within a sibling ImageView?

Basic structure:

container = ui.View()
    imageview = ui.ImageView()
    imageview.image = ui.Image.from_data(photos.pick_image(raw_data=True))
    imageview.flex = 'WH'
    imageview.content_mode = `ui.CONTENT_SCALE_ASPECT_FIT`
    touchview = ui.View()
    touchview.width = ?
    touchview.height = ?

Thank you.

omz

I hope this little demo script/workflow makes these things a bit easier to understand:

http://www.editorial-workflows.com/workflow/5688890476199936/idOllauYutA

In general, when you need to resize a view dynamically, the layout method in a custom view is a good place to do that because it gets called automatically when you rotate the device, etc. In the layout method, you can get the size of the view by using the bounds (or width, height) attribute.

In the demo, I've used the layout method to calculate the scaled size and position of the image (based on the size of the container), and to align an overlay view on top of it with the image underneath. When you touch the overlay, a simple red square shows up under your finger (obviously not very useful, but shows how you can handle touches on the overlay).

The ui.get_screen_size function isn't really intended to do layout with, the primary purpose is really to find out what kind of device you're running on, and it's intended to always return the same value, regardless of orientation.

philg

This was a huge step forward, Ole. Thank you. I managed to do most touch events already, but there are a few questions left, which I believe are minor:

On that setup, how do I add ButtonItems to the main view that interact with the touch events created in the image_view? For example, right now I managed to draw a square with the touch_moved events:

def touch_moved(self, touch):
    loc = touch.location
    if loc[0] < self.indicator_start.center[0]:
        self.crop_area.x = loc[0]
    if loc[1] < self.indicator_start.center[1]:
        self.crop_area.y = loc[1]
    self.crop_area.width = abs(loc[0] - self.indicator_start.center[0])
    self.crop_area.height = abs(loc[1] - self.indicator_start.center[1])

So far I only managed to add ButtonItems in the main view, but I'm looking to set them as False by default and True only after the touch_ended events from the image_view.

omz

You can only add ButtonItems to a view that is "presented" (i.e. the main view), but you could access them from a subview via self.superview.right_button_items...

philg

self.crop_button = ui.ButtonItem(title='Crop', enabled=False)
self.superview.right_button_items = [self.crop_button]
self.clear_button = ui.ButtonItem(title='Clear', enabled=False)
self.superview.left_button_items = [self.clear_button]

In the DragView, if i add this to the __init__:

AttributeError: 'NoneType' object has no attribute 'right_button_items_'

But if I had to the draw function it works fine. I wonder if that's an expected behavior.

philg

Ok, now I'm trying to improve the workflow and add a touch action that allows the user to drag the crop area. So I changed the CropArea custom view a little:

class CropArea(ui.View):
    def __init__(self):
        self.hidden = True
        self.touch_enabled = False
        self.background_color = (1,0.54,0.24,0.3)
        self.bounds = (0,0,2,2)
    def touch_began(self,touch):
        cx, cy = self.center
        tx, ty = touch.location
        self.dxy = tx - cx, ty - cy
    def touch_moved(self,touch):
        self.center = self.dxy[0] + touch.location[0], self.dxy[1] + touch.location[1]
        self.superview.indicator_start.center = self.x, self.y
        self.superview.indicator_end.center = self.x+self.width,self.y+self.height
        self.superview.crop_pixels.x = self.x
        self.superview.crop_pixels.y = self.y + self.height

I also included a self.crop_area.touch_enabled = True to the touch_ended events of the DragView. As you may guess, it works, however, the crop area flickers between 2 different positions when you move your finger, as shown in this debug I ran:

Touch began
('CENTER', 160.25, 258.75)
('TOUCH', 51.0, 60.0)
('DIFF', (-109.25, -198.75))
Touch Moved
('CENTER', (-52.75, -137.75))
('TOUCH', (269.5, 457.5))
Touch Moved
('CENTER', (167.25, 258.75))
('TOUCH', (56.5, 61.0))
Touch Moved
('CENTER', (-44.75, -137.25))
('TOUCH', (276.5, 457.5))
Touch Moved
('CENTER', (176.25, 259.25))
('TOUCH', (64.5, 61.5))
Touch Moved
('CENTER', (-37.75, -137.25))
('TOUCH', (285.5, 458.0))
Touch Moved
('CENTER', (181.75, 259.25))
('TOUCH', (71.5, 61.5))
Touch Moved
('CENTER', (-33.25, -137.25))
('TOUCH', (291.0, 458.0))
Touch Moved
('CENTER', (184.25, 259.25))
('TOUCH', (76.0, 61.5))

These prints ran on the CropArea view only, so I guess that when the user moves the finger, it registers 2 touches, 1 based on the DragView and other based on the CropArea. I wonder why they affect the handlers from CropArea.

philg

Another debug proves the above, just look for the prev_location:

Touch began
('CENTER', 228.75, 272.0)
('TOUCH', 35.0, 38.0)
('DIFF', (-193.75, -234.0))
Touch Moved
('CENTER', (-155.75, -195.0))
('TOUCH', (422.5, 506.0))
('PREV', (419.5, 505.0))
('ID', 367777440)
('TIME', 207510.7054166667)
Touch Moved
('CENTER', (235.25, 272.0))
('TOUCH', (38.0, 39.0))
('PREV', (31.5, 39.0))
('ID', 367777440)
('TIME', 207510.72173520835)
Touch Moved
('CENTER', (-147.25, -194.5))
('TOUCH', (429.0, 506.0))
('PREV', (420.5, 505.5))
('ID', 367777440)
('TIME', 207510.73909541668)
Touch Moved
('CENTER', (243.75, 273.0))
('TOUCH', (46.5, 39.5))
('PREV', (38.0, 38.5))
('ID', 367777440)
('TIME', 207510.755424125)
Touch Moved
('CENTER', (-137.75, -194.0))
('TOUCH', (437.5, 507.0))
('PREV', (428.0, 506.5))
('ID', 367777440)
('TIME', 207510.77168987502)
philg

Ok, apparently I remarkably missed to ask the question. But is there a way for these touch_enabled views to work together? CropArea is a subview of DragView. They're both registering touch events and messing things up. If I set DragView.touch_enabled to False, then CropArea stops working as well. I tried to set the same frame to both views, so the x and y would remain the same, but I failed miserably and the CropArea still flickers (now, apparently, the DragView event simply sends it to some dark and deep place).

I wonder if my only solution would be to create another View to handle editing, it would be a subview of DemoView (therefore, a sibling of DragView, but one layer above) and would affect its drawn elements.

philg

Tried that and it still flickers :(

What can I do to help you, Ole?

philg

Apparently I worked things out (:

http://www.editorial-workflows.com/workflow/4505745634623488/Jl7G4J6ikPM