Forum Archive

Text display help

Bumbo Cactoni

How do I edit a file while seeing what I'm editing, using python? Kinda like a text editor.

ccc

Please send a screenshot of your code in Pythonista.

stephen

Edit:

removed f.close() and switched to using enumerate in forloop with history.

@Bumbo-Cactoni

a lil messy but its a simple text editor.. i left out the input view for passing what file but it shows undo/redo, and opens and displays plain text and saves changes on close.

import ui

def new_button(frame, title, action):
    btn=ui.Button()
    btn.frame=frame
    btn.background_color='#686868'
    btn.tint_color='#000000'
    btn.title=title
    btn.action=action
    return btn

class Text_Editor:
    def __init__(self, main, tv, text_file, *args, **kwargs):
        # undo/redo View
        self.main=main
        self.text_file=text_file
        self.active_file=None
        self.history_view = ui.TextView(
                text='UNDO HISTORY',
                frame=(250, 275, 300, 400),
                editable=False,
                border_width=5,
                background_color='#dedede')

        # Undo Button
        undo_btn=new_button(frame=(25, 2, 100, 46), title='undo', action=self.undo)
        main.add_subview(undo_btn)

        # Redo Button
        redo_btn=new_button(frame=(127, 2, 100, 46), title='redo', action=self.redo)
        main.add_subview(redo_btn)

        # Close Button
        close_btn=new_button(frame=(525, 2, 100, 46), title='close', action=self.close)
        main.add_subview(close_btn)

        self.history=[]
        self.current_index=len(self.history)
        self.textview=tv
        self.textview_should_begin_editing(self.textview)

    def append(self, text):
        self.main.remove_subview(self.history_view)
        if self.current_index < len(self.history):
            self.history=self.history[:self.current_index]
        self.current_index=len(self.history)
        self.history.append(text)
        self.current_index+=1

        self.update_history(self.current_index)

    def update_history(self, index):
        self.history_view.text=''
        for i, x in enumerate(self.history):
            if i == index:
                self.history_view.text+=str(i)+' '+x+' <--'+'\n'
            elif x == self.history[-1] and index == len(self.history):
                self.history_view.text+=str(i)+' '+x+' <--'+'\n'
            else:
                self.history_view.text+=str(i)+' '+x+'\n'

    def close(self, sender):
        with open(self.text_file, 'w') as f:
            f.writelines(self.textview.text.splitlines(keepends=True))
        sender.superview.close()

    def redo(self, sender):
        if self.current_index == len(self.history):
            return
        if self.current_index < len(self.history):
            self.current_index+=1
            self.textview.text = self.history[self.current_index-1]
        self.update_history(self.current_index-1)

    def undo(self, sender):
        if self.current_index == 0:
            return
        self.main.add_subview(self.history_view)

        self.current_index-=1
        self.textview.text = self.history[self.current_index-1]
        self.update_history(self.current_index-1)

    def textview_should_begin_editing(self, textview):
        if textview.text=='' and self.active_file ==None:
            if self.text_file:
                with open(self.text_file, 'r') as f:
                    self.active_file=f
                    for line in f:
                        textview.text+=line
        self.textview=textview
        return True

    def textview_did_change(self, textview):
        self.append(textview.text)
        self.update_history(self.current_index)
        pass

class Main(ui.View):
    def __init__(self, *args, **kwargs):
        text_file='./TextView.py'

        # Main Window
        self.width = 650
        self.height = 1000
        self.background_color="#8b8b8b"
        self.update_interval=1

        # Text Input Window
        self.tv = ui.TextView(
                text='',
                frame=(25, 50, 600, 875),
                background_color='#dedede')
        self.add_subview(self.tv)
        self.tv.delegate=Text_Editor(self, self.tv, text_file)



if __name__ == '__main__':
    Main().present('sheet', hide_title_bar=True)
ccc
        i=0
        for x in self.history:
            [...]
            i+=1

--> for i, x in enumerate(self.history):
https://docs.python.org/3/library/functions.html#enumerate


with open(self.text_file, 'w') as f:
            f.writelines(self.textview.text.splitlines(keepends=True))
            f.close()  # <-- this line is not needed.  `with open() as` automates the call to `close()`.

https://docs.python.org/3/tutorial/inputoutput.html#reading-and-writing-files

stephen

@ccc said:

python i=0 for x in self.history: [...] i+=1
--> for i, x in enumerate(self.history):
https://docs.python.org/3/library/functions.html#enumerate

Good call! i keep forgetting about enumerate()

@ccc said:

python with open(self.text_file, 'w') as f: f.writelines(self.textview.text.splitlines(keepends=True)) f.close() # <-- this line is not needed. `with open() as` automates the call to `close()`.
https://docs.python.org/3/tutorial/inputoutput.html#reading-and-writing-files

f.close() is more of a typo. i tend to write everything and jump around alot because of my Anxiety and ADHD 😜🤕 and i missstuff during refactoring. thankyou for the input and corection 😁 ill change out for enumerate and get rid of the pointless f.close()

ccc

Do you have a file in the current working directory with the name TextView.py?

Bumbo Cactoni

Never mind. I found what to edit

stephen

@Bumbo-Cactoni said:

Never mind. I found what to edit

what happened?

Bumbo Cactoni

I couldn’t find what variable to edit to get to the right file, but now I have

stephen

@Bumbo-Cactoni oh ok i meant to Make note of where that was originally..

i think i saw younsay somthing about it slowing down when you typed?

Bumbo Cactoni

@stephen
I don’t know exactly why, but the longer I type for, the slower the program gets. It’s not a big issue, as I can just close the text editor and reopen it.

mikael

@Bumbo-Cactoni, I am sure this was just a quick prototype, as there is a lot happening every time you type a single letter. @stephen, maybe as a quick fix, remove all the history stuff as it is not necessary here? Also, consider saving to file in a background thread, and only whenever there is e.g. a 500 ms break in the typing action.

... Sorry, disregard the second point, as I see the saving only happens when closing.

Bumbo Cactoni

@stephen
I agree with mikael. His point is valid: I don’t really need the history stuff, and just in case I do, I can put all the history stuff in a separate text file, so I can put it in if needed.

Bumbo Cactoni

Alright. I have figured out how to make it work faster, without deleting the code. Just make all the update_history, undo, and redo into comments:

```
import ui

def new_button(frame, title, action):
btn=ui.Button()
btn.frame=frame
btn.background_color='#686868'
btn.tint_color='#000000'
btn.title=title
btn.action=action
return btn

class Text_Editor:
def init(self, main, tv, text_file, args, *kwargs):
# undo/redo View
self.main=main
self.text_file=text_file
self.active_file=None
self.history_view = ui.TextView(
text='UNDO HISTORY',
frame=(250, 275, 300, 400),
editable=False,
border_width=5,
background_color='#dedede')

    # Undo Button

undo_btn=new_button(frame=(25, 2, 100, 46), title='undo', action=self.undo)

main.add_subview(undo_btn)

    # Redo Button

redo_btn=new_button(frame=(127, 2, 100, 46), title='redo', action=self.redo)

main.add_subview(redo_btn)

    # Close Button
    close_btn=new_button(frame=(525, 2, 100, 46), title='close', action=self.close)
    main.add_subview(close_btn)

    self.history=[]
    self.current_index=len(self.history)
    self.textview=tv
    self.textview_should_begin_editing(self.textview)

def append(self, text):
    self.main.remove_subview(self.history_view)
    if self.current_index < len(self.history):
        self.history=self.history[:self.current_index]
    self.current_index=len(self.history)
    self.history.append(text)
    self.current_index+=1

self.update_history(self.current_index)

def update_history(self, index):

self.history_view.text=''

for i, x in enumerate(self.history):

if i == index:

self.history_view.text+=str(i)+' '+x+' <--'+'\n'

elif x == self.history[-1] and index == len(self.history):

self.history_view.text+=str(i)+' '+x+' <--'+'\n'

else:

self.history_view.text+=str(i)+' '+x+'\n'

def close(self, sender):
    with open(self.text_file, 'w') as f:
        f.writelines(self.textview.text.splitlines(keepends=True))
    sender.superview.close()

def redo(self, sender):

if self.current_index == len(self.history):

return

if self.current_index < len(self.history):

self.current_index+=1

self.textview.text = self.history[self.current_index-1]

self.update_history(self.current_index-1)

def undo(self, sender):

if self.current_index == 0:

return

self.main.add_subview(self.history_view)

self.current_index-=1

self.textview.text = self.history[self.current_index-1]

self.update_history(self.current_index-1)

def textview_should_begin_editing(self, textview):
    if textview.text=='' and self.active_file ==None:
        if self.text_file:
            with open(self.text_file, 'r') as f:
                self.active_file=f
                for line in f:
                    textview.text+=line
    self.textview=textview
    return True

def textview_did_change(self, textview):
    self.append(textview.text)

self.update_history(self.current_index)

    pass

class Main(ui.View):
def init(self, args, *kwargs):

    """THIS IS WHERE YOU EDIT THE FILE!"""

    text_file='./file.txt'


    # Main Window
    self.width = 650
    self.height = 1000
    self.background_color="#8b8b8b"
    self.update_interval=1

    # Text Input Window
    self.tv = ui.TextView(
            text='',
            frame=(25, 50, 600, 875),
            background_color='#dedede')
    self.add_subview(self.tv)
    self.tv.delegate=Text_Editor(self, self.tv, text_file)

if name == 'main':
Main().present('sheet', hide_title_bar=True) ```

stephen

@Bumbo-Cactoni im not 100% on understanding exactly what you are going for so ive just beenthrowin stuff together to help ya out. ive been running under the assumption of a text editor lol thats why i included the undo funtionality, and currently playing around with a custom file picker.

as for your workaroud, @mikael was correct about history update. but you only needed to remove the history_view stuff you can keep the undo-redo stuff if you like. and i would suggest not storing history in a file unless yourvwanting to keep a log of changes. i say this because history is just the ontainer holding the diferent states of your text for undo-redo. and changes often and you wouldnt want to open and close a file lot to keep it updated.

Bumbo Cactoni

@stephen
Ok, thanks! Also, yes, I was aiming for a text editor! If you figure out the custom file picker, let me know! I think that would be awesome to have in a text editor!

mikael

@Bumbo-Cactoni, we still do not know why you need this type of a text editor, and do not just use Pythonista’s editor.

Use google or other search engine to search for ”omz forum file picker dialog”, and you will find something, I am sure.

stephen

@Bumbo-Cactoni said:

@stephen
Ok, thanks! Also, yes, I was aiming for a text editor! If you figure out the custom file picker, let me know! I think that would be awesome to have in a text editor!

here is the text editor with the file picker added at the start. this was a fun one so i may contenu to add onto this myself. Though i do agree with @mikael.. Im not picturing why you want this unless just for learning? or is this meant for some type of note taking mecanic to a game or app?

import ui
import os
from objc_util import ObjCInstance, ObjCClass
from operator import attrgetter
import time
import threading
import functools
import ftplib
import re

# http://stackoverflow.com/a/6547474
def human_size(size_bytes):
    '''Helper function for formatting human-readable file sizes'''
    if size_bytes == 1:
        return "1 byte"
    suffixes_table = [('bytes',0),('KB',0),('MB',1),('GB',2),('TB',2), ('PB',2)]
    num = float(size_bytes)
    for suffix, precision in suffixes_table:
        if num < 1024.0:
            break
        num /= 1024.0
    if precision == 0:
        formatted_size = "%d" % num
    else:
        formatted_size = str(round(num, ndigits=precision))
    return "%s %s" % (formatted_size, suffix)

class TreeNode (object):
    def __init__(self):
        self.expanded = False
        self.children = None
        self.leaf = True
        self.title = ''
        self.subtitle = ''
        self.icon_name = None
        self.level = 0
        self.enabled = True

    def expand_children(self):
        self.expanded = True
        self.children = []

    def collapse_children(self):
        self.expanded = False

    def __repr__(self):
        return '<TreeNode: "%s"%s>' % (self.title, ' (expanded)' if self.expanded else '')

class FileTreeNode (TreeNode):
    def __init__(self, path, show_size=True, select_dirs=True,
           file_pattern=None):
        TreeNode.__init__(self)
        self.path = path
        self.title = os.path.split(path)[1]
        self.select_dirs = select_dirs
        self.file_pattern = file_pattern
        is_dir = os.path.isdir(path)
        self.leaf = not is_dir
        ext = os.path.splitext(path)[1].lower()
        if is_dir:
            self.icon_name = 'iob:filing_24'
        elif ext == '.py':
            self.icon_name = 'iob:code_24'
        elif ext == '.pyui':
            self.icon_name = 'UI File'
        elif ext in ('.png', '.jpg', '.jpeg', '.gif'):
            self.icon_name = 'iob:image_24'
        elif ext == '.txt':
            self.icon_name = 'iob:compose_24'
        elif ext == '.md':
            self.icon_name = 'iob:ios7_bookmarks_outline_24'
        elif ext == '.html':
            self.icon_name = 'iob:leaf_24'
        elif ext == '.css':
            self.icon_name = 'CSS File'
        else:
            self.icon_name = 'FileOther'
        self.show_size = show_size
        if not is_dir and show_size:
            self.subtitle = human_size((os.stat(self.path).st_size))
        if is_dir and not select_dirs:
            self.enabled = False
        elif not is_dir:
            filename = os.path.split(path)[1]
            for x in file_pattern:
                if x in filename:
                    self.enabled = True
                    break


    @property
    def cmp_title(self):
        return self.title

    def expand_children(self):
        if self.children is not None:
            self.expanded = True
            return
        files = os.listdir(self.path)
        children = []
        for filename in files:
            if filename.startswith('.'):
                continue
            full_path = os.path.join(self.path, filename)
            node = FileTreeNode(full_path, self.show_size, self.select_dirs, self.file_pattern)
            node.level = self.level + 1
            children.append(node)
        self.expanded = True
        self.children = sorted(children, key=attrgetter('leaf', 'cmp_title'))


class TreeDialogController (object):
    def __init__(self, main, root_node, x, y, allow_multi=False, async_mode=False):
        self.main=main
        self.async_mode = async_mode
        self.allow_multi = allow_multi
        self.selected_entries = None
        self.table_view = ui.TableView()
        self.table_view.frame = (0, 0, 500, 500)
        self.table_view.data_source = self
        self.table_view.delegate = self
        self.table_view.flex = 'WH'
        self.table_view.border_width=5
        self.table_view.corner_radius=8.0
        self.table_view.allows_multiple_selection = False
        self.table_view.background_color='#daba8b'
        self.table_view.tint_color = '#ff00d1'
        self.view = ui.View(frame=(x, y, 500, 500))
        self.view.add_subview(self.table_view)
        self.view.name = root_node.title
        self.busy_view = ui.View(frame=self.view.bounds, flex='WH', background_color=(0, 0, 0, 0.35))
        hud = ui.View(frame=(self.view.center.x - 50, self.view.center.y - 50, 100, 100))
        hud.background_color = (0, 0, 0, 0.7)
        hud.corner_radius = 8.0
        hud.flex = 'TLRB'
        spinner = ui.ActivityIndicator()
        spinner.style = ui.ACTIVITY_INDICATOR_STYLE_WHITE_LARGE
        spinner.center = (50, 50)
        spinner.start_animating()
        hud.add_subview(spinner)
        self.busy_view.add_subview(hud)
        self.busy_view.alpha = 0.0
        self.view.add_subview(self.busy_view)
        self.done_btn = ui.ButtonItem(title='Done', action=self.done_action)
        if self.allow_multi:
            self.view.right_button_items = [self.done_btn]
        self.done_btn.enabled = False
        self.root_node = root_node
        self.entries = []
        self.flat_entries = []
        if self.async_mode:
            self.set_busy(True)
            t = threading.Thread(target=self.expand_root)
            t.start()
        else:
            self.expand_root()

    def expand_root(self):
        self.root_node.expand_children()
        self.set_busy(False)
        self.entries = self.root_node.children
        self.flat_entries = self.entries
        self.table_view.reload()

    def flatten_entries(self, entries, dest=None):
        if dest is None:
            dest = []
        for entry in entries:
            dest.append(entry)
            if not entry.leaf and entry.expanded:
                self.flatten_entries(entry.children, dest)
        return dest

    def rebuild_flat_entries(self):
        self.flat_entries = self.flatten_entries(self.entries)

    def tableview_number_of_rows(self, tv, section):
        return len(self.flat_entries)

    def tableview_cell_for_row(self, tv, section, row):
        cell = ui.TableViewCell()
        cell.background_color='#daba8b'
        entry = self.flat_entries[row]
        level = entry.level - 1
        image_view = ui.ImageView(frame=(44 + 20*level, 5, 34, 34))
        label_x = 44+34+8+20*level
        label_w = cell.content_view.bounds.w - label_x - 8
        if entry.subtitle:
            label_frame = (label_x, 0, label_w, 26)
            sub_label = ui.Label(frame=(label_x, 26, label_w, 14))
            sub_label.font = ('<System>', 12)
            sub_label.text = entry.subtitle
            sub_label.text_color = '#2a8a99'
            cell.content_view.add_subview(sub_label)
        else:
            label_frame = (label_x, 0, label_w, 44)
        label = ui.Label(frame=label_frame)
        if entry.subtitle:
            label.font = ('<System>', 15)
        else:
            label.font = ('<System>', 18)
        label.text = entry.title
        label.flex = 'W'
        cell.content_view.add_subview(label)
        if entry.leaf and not entry.enabled:
            label.text_color = '#66006b'
        cell.content_view.add_subview(image_view)
        if not entry.leaf:
            has_children = entry.expanded
            btn = ui.Button(image=ui.Image.named('iob:minus_round_24' if has_children else 'iob:plus_round_24'))
            btn.frame = (20*level, 0, 44, 44)
            btn.action = self.expand_dir_action
            cell.content_view.add_subview(btn)
        if entry.icon_name:
            image_view.image = ui.Image.named(entry.icon_name)
        else:
            image_view.image = None
        cell.selectable = entry.enabled
        return cell

    def row_for_view(self, sender):
        '''Helper to find the row index for an 'expand' button'''
        cell = ObjCInstance(sender)
        while not cell.isKindOfClass_(ObjCClass('UITableViewCell')):
            cell = cell.superview()
        return ObjCInstance(self.table_view).indexPathForCell_(cell).row()

    def expand_dir_action(self, sender):
        '''Invoked by 'expand' button'''
        row = self.row_for_view(sender)
        entry = self.flat_entries[row]
        if entry.expanded:
            sender.image = ui.Image.named('iob:plus_round_24')
        else:
            sender.image = ui.Image.named('iob:minus_round_24')
        self.toggle_dir(row)
        self.update_done_btn()

    def toggle_dir(self, row):
        '''Expand or collapse a folder node'''
        entry = self.flat_entries[row]
        if entry.expanded:
            entry.collapse_children()
            old_len = len(self.flat_entries)
            self.rebuild_flat_entries()
            num_deleted = old_len - len(self.flat_entries)
            deleted_rows = range(row + 1, row + num_deleted + 1)
            self.table_view.delete_rows(deleted_rows)
        else:
            if self.async_mode:
                self.set_busy(True)
                expand = functools.partial(self.do_expand, entry, row)
                t = threading.Thread(target=expand)
                t.start()
            else:
                self.do_expand(entry, row)

    def do_expand(self, entry, row):
        '''Actual folder expansion (called on background thread if async_mode is enabled)'''
        entry.expand_children()
        self.set_busy(False)
        old_len = len(self.flat_entries)
        self.rebuild_flat_entries()
        num_inserted = len(self.flat_entries) - old_len
        inserted_rows = range(row + 1, row + num_inserted + 1)
        self.table_view.insert_rows(inserted_rows)

    def tableview_did_select(self, tv, section, row):

        self.update_done_btn()

    def tableview_did_deselect(self, tv, section, row):
        self.update_done_btn()

    def update_done_btn(self):
        '''Deactivate the done button when nothing is selected'''
        selected = [self.flat_entries[i[1]] for i in self.table_view.selected_rows if self.flat_entries[i[1]].enabled]
#        print(selected)
        if selected and not self.allow_multi:
            self.done_action(None)
        else:
            self.done_btn.enabled = len(selected) > 0

    def set_busy(self, flag):
        '''Show/hide spinner overlay'''
        def anim():
            self.busy_view.alpha = 1.0 if flag else 0.0
        ui.animate(anim)

    def done_action(self, sender):
        self.selected_entries = [self.flat_entries[i[1]] for i in self.table_view.selected_rows if self.flat_entries[i[1]].enabled]
        self.main.open_file(self, self.selected_entries[0].path)


def file_picker_dialog(parent, x, y, title=None, root_dir=None, multiple=False,
                       select_dirs=False, file_pattern=None, show_size=True):
    if root_dir is None:
        root_dir = os.path.expanduser('..')
    if title is None:
        title = os.path.split(root_dir)[1]
    root_node = FileTreeNode(root_dir, show_size, select_dirs, file_pattern)
    root_node.title = title or ''
    picker = TreeDialogController(parent, root_node, x, y, allow_multi=multiple)
    parent.add_subview(picker.view)
    picker.view.wait_modal()
    if picker.selected_entries is None:
        return None
    paths = [e.path for e in picker.selected_entries]
    if multiple:
        return paths
    else:
        return paths[0]


def new_button(frame, title, action):
    btn=ui.Button()
    btn.frame=frame
    btn.background_color='#686868'
    btn.tint_color='#000000'
    btn.title=title
    btn.action=action
    return btn




class Text_Editor:
    def __init__(self, main, tv, text_file, *args, **kwargs):
        # undo/redo View
        self.main=main
        self.text_file=text_file
        self.active_file=None

        # Undo Button
        undo_btn=new_button(frame=(25, 2, 100, 46), title='undo', action=self.undo)
        main.add_subview(undo_btn)

        # Redo Button
        redo_btn=new_button(frame=(127, 2, 100, 46), title='redo', action=self.redo)
        main.add_subview(redo_btn)

        # Close Button
        close_btn=new_button(frame=(525, 2, 100, 46), title='close', action=self.close)
        main.add_subview(close_btn)

        self.history=[]
        self.current_index=len(self.history)
        self.textview=tv
        self.textview_should_begin_editing(self.textview)

    def append(self, text):
        if self.current_index < len(self.history):
            self.history=self.history[:self.current_index]
        self.current_index=len(self.history)
        self.history.append(text)
        self.current_index+=1

    def close(self, sender):
        with open(self.text_file, 'w') as f:
            f.writelines(self.textview.text.splitlines(keepends=True))
        sender.superview.close()

    def redo(self, sender):
        if self.current_index == len(self.history):
            return
        if self.current_index < len(self.history):
            self.current_index+=1
            self.textview.text = self.history[self.current_index-1]


    def undo(self, sender):
        if self.current_index == 0:
            return
        self.current_index-=1
        self.textview.text = self.history[self.current_index-1]

    def textview_should_begin_editing(self, textview):
        return True

    def textview_did_change(self, textview):
        self.append(textview.text)

class Main(ui.View):
    def __init__(self, *args, **kwargs):
        text_file=None

        # Main Window
        self.width = 650
        self.height = 1000
        self.background_color="#a18967"
        self.update_interval=1

        self.tv = ui.TextView(
            frame=(25, 50, 600, 875), background_color='#daba8b')

        self.file_picker=file_picker_dialog(
            self, x=self.width/2-250, y=self.height/2-250,
            file_pattern=['.txt', '.md', '.py'])

    def file_view(self):
        self.add_subview(
            FilePicker(frame=(50,100, self.width-100, self.height-200)))

    def open_file(self, child_view,  file):


        self.add_subview(self.tv)
        self.tv.delegate=Text_Editor(self, self.tv, file)
        with open(file, 'r') as f:
            for line in f:
                self.tv.text+=line

if __name__ == '__main__':
    Main().present('sheet', hide_title_bar=True)

Bumbo Cactoni

@stephen
Yes, I am going to attempt to implement it in an app I am going to try to make.

stephen

@Bumbo-Cactoni said:

@stephen
Yes, I am going to attempt to implement it in an app I am going to try to make.

since ill probably play with it a little bit we can co-op a bit on it if you like

Bumbo Cactoni

@stephen
Yes, I would like that. Right now is not a super great time, though, as I still have to finish my school year. When I am done with that (in about 10 days), I would love to co-op on it.

However, I do have a question. How would I create a pushable button in Pythonista? I know tkinter doesn’t work, since it is designed for computer displays. Tkinter is really the only way I know how to create a button, so I may require some assistance.

stephen

@Bumbo-Cactoni said:

@stephen
Yes, I would like that. Right now is not a super great time, though, as I still have to finish my school year. When I am done with that (in about 10 days), I would love to co-op on it.

However, I do have a question. How would I create a pushable button in Pythonista? I know tkinter doesn’t work, since it is designed for computer displays. Tkinter is really the only way I know how to create a button, so I may require some assistance.

do you mean creating a button within Pythonista's Editor View itself or one in a CustomView fom ui or in a scene.Node? iether way i can help you out im sure. ui has a built-in ui.Button, For Pythonista's Editor UIView we would use objc_util im sure and for scene ive alredy created own ButtonNode class that i have in my "cook book"

mikael

@Bumbo-Cactoni:


import ui

v = ui.View()

btn = ui.Button(
    title="Push",
    flex='RTLB',
    background_color='grey',
    tint_color='black'
)

def action(sender):
    sender.title = 'Pushed'

btn.action = action
btn.width = 200
btn.height = 40
btn.center = (50, 50)

v.add_subview(btn)
v.present('fullscreen')

mikael

@Bumbo-Cactoni, to explain the obscure part:

  • View v is by default initially sized at 100 x 100 pixels
  • So placing the button center at (50, 50) means it is in the center of the view at this point
  • Then, because the flex is set to ”Right Top Left Bottom”, i.e. all sides, they will scale with the view, effectively keeping the button in center when the view is presented and scaled to fill the screen
  • And it will continue to stay in the center if you e.g. rotate the device

I think the initial size 100 x 100 was chosen at least partly so that if you want, you can think the locations of subviews as percentages.