Dynamic views and Images in the ui module
I've been trying to create an interface prompts for a Camera Roll pic (photos module) and displays it, which is fairly simple using
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".
ImageView.image.sizewill give me the original dimensions, while I want the dimensions after the
ui.content_modeis applied. My best bet right now is to run a function that calculates the outcome from the
content_modebased 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?
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 = ?
I hope this little demo script/workflow makes these things a bit easier to understand:
In general, when you need to resize a view dynamically, the
layoutmethod in a custom view is a good place to do that because it gets called automatically when you rotate the device, etc. In the
layoutmethod, you can get the size of the view by using the
In the demo, I've used the
layoutmethod 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).
ui.get_screen_sizefunction 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.
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
def touch_moved(self, touch): loc = touch.location if loc < self.indicator_start.center: self.crop_area.x = loc if loc < self.indicator_start.center: self.crop_area.y = loc self.crop_area.width = abs(loc - self.indicator_start.center) self.crop_area.height = abs(loc - self.indicator_start.center)
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_endedevents from the
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.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
AttributeError: 'NoneType' object has no attribute 'right_button_items_'
But if I had to the
drawfunction it works fine. I wonder if that's an expected behavior.
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 + touch.location, self.dxy + touch.location 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 = Trueto the
touch_endedevents 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.
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)
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_enabledto 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.
Tried that and it still flickers :(
What can I do to help you, Ole?
Apparently I worked things out (: