• mcriley821

    I came across a weird difference of colors by passing in an un-normalized RGB tuple to the ui.set_color function. I was wanting just regular green (#00ff00) but instead I got a neon green.

    It happens anytime the green value is greater than 1 in the tuple. It stays the same after 2, so it’s almost as if it’s brightness is mapped from 1-> 2.

    What color space is this? I want to take full advantage of this in the color picker!

    import ui
    
    
    class Colors (ui.View):
        def __init__(self, *args, **kwargs):
            return super().__init__(self, *args, **kwargs)
        
        def draw(self):
            neon = ui.Path.rect(5, 5, 100, 100)
            ui.set_color((0, 2, 0))
            neon.fill()
            
            normal = ui.Path.rect(110, 5, 100, 100)
            ui.set_color(("#00ff00"))
            normal.fill()
    
    
    example = Colors(bg_color="white")
    example.present() 
    

    posted in Pythonista read more
  • mcriley821

    @JonB @cvp
    Thank you for the suggestions! I ended up going the easy way and requiring to pass in the master view. Kept the TableView hidden on the master (and thus un-touchable) until the combobox is touched.

    posted in Pythonista read more
  • mcriley821

    Hello,

    I’m trying to make a custom UI view that allows the user to select an option from a drop down box (like a custom ComboBox).

    Everything seems to be working okay, until I actually try to select a row in the TableView. I know it has to do with the way that iOS does hit-testing for views, but I can’t think of an elegant way to get around this. The only thing I can think of is to customize the root view also (but I want to avoid that so I can simply import it to work with any view ootb).

    Essentially, the root view receives the location of the touch, and it then asks its subviews if they contain that touch in their bounds. The custom view then returns False since the touch isn’t in its bounds, although visually the touch is on the TableView.

    Apologies for any formatting confusion/differences (I’m on iPhone), and for any mistakes/issues I’m not completely finished yet!

    Here’s the code:

    import ui
    
    
    """ComboBox is a ui View class that allows a
    string to be chosen from a list of choices by
    tapping on the dropbox button."""
    class ComboBox (ui.View):
        """Initializing of ComboBox:
            Accepts all kwargs of ui.View, as well as:
                font -> Chosen string font
                choices -> List of choice strings
                selected_index -> Initial choice index
                padding -> Internal padding around the label and dropdown button
                            button_tint -> Color of the dropbox button"""
        def __init__(self, *args, **kwargs):
            self.font = kwargs.pop(
                'font', ('<System>', 20))
            self.text_color = kwargs.pop(
                'text_color', "black")
            self.choices = kwargs.pop(
                'choices', [""])
            self.selected_index = kwargs.pop(
                'selected_index', 0)
            self.padding = kwargs.pop(
                'padding', 3)
            self.button_tint = kwargs.pop(
                'button_tint', "grey")
            kwargs.setdefault('border_width', 1.0)
            kwargs.setdefault(
                'border_color', '#e5e5e5')
            kwargs.setdefault('corner_radius', 3.5)
            ui.View.__init__(self, *args, **kwargs)
            
            # button for the dropbox
            _x = self.width - self._padding - 30
            _h = self.height - self._padding * 2
            self.drop_button = ui.Button(
                bg_color=self.bg_color,
                frame=(_x, self._padding, 
                             self.height, _h),
                image=ui.Image('iow:arrow_down_b_24'),
                tint_color=self.button_tint,
                action=self.dropbox_should_open
            )
            
            # label for selected item
            # default to item 0
            _w = self.width - self.drop_button.width - self._padding * 2
            self.selected_label = ui.Label(
                bg_color=self.bg_color,
                alignment=ui.ALIGN_CENTER,
                font=self.font,
                text=self.choices[self.selected_index],
                frame=(self._padding, self._padding, 
                             _w, _h),
                text_color=self.text_color
            )
            
            # dropbox
            _row_h = ui.measure_string(
                self.choices[0], font=self.font)[1]
            _row_h += self._padding
            _h *= 5 if len(self.choices) > 5 else len(self.choices)
            self.dropbox = ui.TableView(
                bg_color=self.bg_color,
                row_height=_row_h,
                seperator_color=self.border_color,
                data_source=self._data_source,
                selected_row=-1,
                allows_selection=True,
                frame=(self._padding, self.height - 1,
                             self.selected_label.width,
                             _h),
                border_color=self.border_color,
                border_width=self.border_width,
                corner_radius=self.corner_radius,
                hidden=True,
                touch_enabled=True
            )
            
            # draw tableview although out of bounds
            obj = self.objc_instance
            obj.setClipsToBounds_(False)
            
            # add subviews
            self.add_subview(self.selected_label)
            self.add_subview(self.drop_button)
            self.add_subview(self.dropbox)
        
        @property
        def selected_index(self):
            return self._selected_index
        
        @selected_index.setter
        def selected_index(self, value):
            if value < len(self.choices):
                self._selected_index = value
                if hasattr(self, 'selected_label'):
                    self.selected_label.text = self.choices[value]
                    self.set_needs_display()
    
        @property
        def selected_text(self):
            return self.choices[self._selected_index]
        
        @property
        def padding(self):
            return self._padding
        
        @padding.setter
        def padding(self, value):
            if value < self.height / 2:
                self._padding = value
                self.set_needs_display()
        
        @property
        def choices(self):
            return self._data_source.items
        
        @choices.setter
        def choices(self, value):
            if type(value) is list and len(value) > 0:
                ds = ui.ListDataSource(value)
                ds.delete_enabled = False
                ds.move_enabled = False
                ds.font=self.font
                ds.action=self.index_changed
                ds.text_color=self.text_color
                self._data_source = ds
        
        def layout(self):
            # selected label layout
            self.selected_label.width = self.width - self.height - self._padding * 2
            self.selected_label.height = self.height - self._padding * 2
            # drop button layout
            self.drop_button.x = self.width - self.height
            self.drop_button.width = self.height - self._padding
            self.drop_button.height = self.height - self._padding * 2
            # dropbox layout
            self.dropbox.width = self.selected_label.width
            self.dropbox.y = self.height
            _h = ui.measure_string(
                self.choices[0], font=self.font)[1]
            _h += self._padding
            _h *= 5 if len(self.choices) > 5 else len(self.choices)
            self.dropbox.height = _h    
        
        def touch_began(self, touch):
            print(touch)
            if self._touch_in_frame(touch, self.selected_label.frame) and touch.phase == "began":
                if self.dropbox.hidden:
                    self.dropbox_should_open(None)
                else:
                    self.dropbox_should_close(None)
    
        @staticmethod
        def _touch_in_frame(touch, frame):
            x, y, w, h = frame
            xmin, xmax = x, x + w
            ymin, ymax = y, y + h
            x, y = touch.location
            if xmin < x < xmax:
                if ymin < y < ymax:
                    return True
            return False
        
        def draw(self):
            # draw the splitter border
            p = ui.Path()
            p.move_to(
                self.selected_label.width + self._padding * 1.5, 0)
            p.line_to(
                self.selected_label.width + self._padding * 1.5,
                self.height)
            p.line_width = self.border_width
            ui.set_color(self.border_color)
            p.stroke()
        
        def dropbox_should_open(self, sender):
            # animate drop box
            if sender:
                sender.action = self.dropbox_should_close
            ui.animate(self.do_dropbox)
        
        def do_dropbox(self):
            self.dropbox.hidden = not self.dropbox.hidden
    
        def dropbox_should_close(self, sender):
            if sender:
                sender.action = self.dropbox_should_open
            ui.animate(self.do_dropbox)
    
        def index_changed(self, sender):
            new_index = sender.selected_row
            if new_index != self.selected_index and new_index != -1:
                self.selected_index = new_index
    
    
    if __name__ == "__main__":
        root = ui.View(bg_color="white")
        combo = ComboBox(bg_color="white",
                                         choices=['test', 'test2'],
                                         corner_radius=3.5,
                                         )
        combo.frame = (50, 50, 275, 40)
        root.add_subview(combo)
        root.present('sheet', hide_title_bar=True)
    
    

    posted in Pythonista read more
  • mcriley821

    Hey all,

    I’m modifying the keyboard example “Special Characters.py” to include all Unicode characters. I did this by scraping the Unicode code point range and category name from Wikipedia. It has a navigation view with all the categories in a scroll view, and then presents all the Unicode characters of that category.

    Anyhow, the problem is that I get spotty printability, but since the character is a valid Unicode character I can’t prevent it from displaying as 🠂. I don’t understand the problem since sys.maxunicode (1114111) suggests this shouldn’t be a problem...

    Is it due to the possibility that the iOS system font doesn’t have a rendering of these glyphs? How can I install Google Noto and access these glyphs (if that’s the issue).

    Code:

    #! python3
    import keyboard
    import ui
    
    with open('uni_blocks.txt', 'r') as block_file:
        blocks = block_file.readlines()
    
    
    class BlocksView (ui.View):
        def __init__(self, *args, **kwargs):
            ui.View.__init__(self, *args, **kwargs)
            self.glyph_sv = None
            self.cat_sv = ui.ScrollView(
                flex='WH',
                frame=self.bounds
            )
            self.buttons = []
            for block in blocks:
                cells = block.split(' ')
                start = cells[0]
                end = cells[1]
                name = ' '.join(cells[2:])
                btn = self.create_button(name)
                btn.action = self.cat_selected
                btn.start = start
                btn.end = end
                self.cat_sv.add_subview(btn)
                self.buttons.append(btn)
            self.add_subview(self.cat_sv)
        
        def layout(self):
            if self.cat_sv.on_screen:
                bw = ui.get_screen_size()[0]
                x, y = 2, 2
                for button in self.buttons:
                    button.frame = (x, y, bw, 20)
                    y += 24
                self.cat_sv.content_size = (0, (len(self.buttons) + 1) * 24 + 40)
        
        def cat_selected(self, sender):
            # present another scroll view of unicode titled buttons
            self.glyph_sv = ui.ScrollView(
                name=sender.title,
                frame=self.bounds,
                flex='WH'
            )
            w = ui.get_screen_size()[0]
            x = y = 2
            rows = 1
            for i in range(int(sender.start, 16), int(sender.end, 16)):
                char = chr(i)
                if char.isprintable() and not char.isspace():
                    btn = self.create_button(chr(i))
                    btn.action = self.insert_char
                    btn.number = i
                    btn.frame = (x, y, 35, 35)
                    x += 39
                    if x > w - 39:
                        x = 2
                        y += 39
                        rows += 1
                    self.glyph_sv.add_subview(btn)
            self.glyph_sv.content_size = (w, (rows + 1) * 39 + 40)
            self.navigation_view.push_view(self.glyph_sv, False)
        
        def create_button(self, name):
            btn = ui.Button(title=name)
            btn.font = ('<System>', 18)
            btn.background_color = (1, 1, 1, 0.1)
            btn.tint_color = 'white'
            btn.corner_radius = 4
            btn.size_to_fit()
            return btn
    
        def insert_char(self, sender):
            if keyboard.is_keyboard():
                keyboard.insert_text(sender.title)
            else:
                print(sender.title, sender.number)
    
    
    def main():
        w = ui.get_screen_size()[0]
        v = BlocksView(
            frame=(0, 0, w, 133),
            name='Categories',
            flex='WH'
        )
        nav_view = ui.NavigationView(v)
        nav_view.title_color = 'white'
        if keyboard.is_keyboard():
            keyboard.set_view(nav_view, 'expanded')
        else:
            # For debugging in the main app:
            nav_view.bg_color = 'black'
            nav_view.bar_tint_color='black'
            nav_view.present('fullscreen', hide_title_bar=True)
    
    
    if __name__ == '__main__':
        main()
    
    

    Source .txt:
    https://pastebin.com/raw/Y9Fsuykq

    Thanks!

    posted in Pythonista read more
  • mcriley821

    @mikael @JonB

    I think I figured out a solution that will work. Since the doubletap gesture was recognizing double-taps and gain access to self by inlining the objc method, I realized that’s all I really needed. I can initialize the TextView with editable=False, enable editing inside the objc method, and start editing. Then in the textview’s delegate, when editing is done, disable editing!

    Thank you guys for the help!

    Here’s the code if anyone is interested:

    from objc_util import *
    import ui
    
    UITap = ObjCClass('UITapGestureRecognizer')
    
    class Node (ui.View):
        class TextViewDelegate (object):
            def textview_did_change(self, tv):
                try:
                    if '\n' == tv.text[-1]:
                        tv.text = tv.text[:-1]
                        tv.end_editing()
                except IndexError:
                    pass
            
            def textview_did_end_editing(self, tv):
                # do whatever with the text
                tv.editable = False
        
        def __init__(self, *args, **kwargs):
            ui.View.__init__(self, *args, **kwargs)
            self.frame = (0,0,*(ui.get_screen_size()))
            self.tv = ui.TextView(
                name='node_label',
                frame=(100, 100, 50, 50),
                delegate=self.TextViewDelegate(),
                bg_color='#b9b9ff',
                editable=False
            )
            
            tvObj = self.tv.objc_instance
            
            def handleTap_(_slf, _cmd, _gesture):
                self.tv.editable = True
                self.tv.begin_editing()
                
            self.target = create_objc_class(
                'fake_target',
                methods=[handleTap_]
                ).new()
        
            self.doubletap = UITap.alloc().initWithTarget_action_(
                self.target,
                'handleTap:'
            )
            self.doubletap.setNumberOfTapsRequired_(2)
            tvObj.addGestureRecognizer_(
                self.doubletap
            )
            self.add_subview(self.tv)
    
        def will_close(self):
            self.doubletap.release()
            self.target.release()
    
    if __name__ == '__main__':
        w, h = ui.get_screen_size()
        view = Node(bg_color='white')
        view.present()
    
    

    posted in Pythonista read more
  • mcriley821

    @cvp TextField is even odder, having no gestureRecognizers. It probably has a view hierarchy with a TextView. Maybe I need to remove gestures from all underlying views of my TextView?

    posted in Pythonista read more
  • mcriley821

    @JonB I’ve seen @mikael’s repo, but based on personal reasons I’d rather not use it. I want to understand how things are working instead of importing.

    Anyhow, inlining the method did not fix the issue. Making tv a self attribute, and changing the method to:

    def handleTap_(_slf, _cmd, _gesture):
        self.tv.begin_editing()
    

    After the first double tap, a single tap suffices to start editing again. I also retained target (self.target).

    posted in Pythonista read more

Internal error.

Oops! Looks like something went wrong!