-
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()
-
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. -
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)
-
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/Y9FsuykqThanks!
-
mcriley821
I think I figured out a solution that will work. Since the
doubletap
gesture was recognizing double-taps and gain access toself
by inlining the objc method, I realized that’s all I really needed. I can initialize the TextView witheditable=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()
-
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?
-
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).