Quick Links: Download Gideros Studio | Gideros Documentation | Gideros Development Center | Gideros community chat | DONATE
selectable text in app? — Gideros Forum

selectable text in app?

Hi I'd like the user to select some app generated text and copy it to his device clipboard. The only way I can think of doing it is through imgui inputText wih readonly flag, but using imgui only for this feature seems a bit overkill.
Is there any other way I am not aware to do this in gideros?

Thank you! :)

Comments

  • piepie Member
    edited October 2023
    That is to save to clipboard, my primary issue is about selecting some text generated by the app, and then save it to clipboard (I guess with application:setClipboard ). I think that textfield object is not selectable though, and I can't think of other options to write text inside a gideros app. To be clear: the act of selecting the right portion of text is part of the game. :)
  • hgy29hgy29 Maintainer
    Gideros UI library has support for selectable text fields

    Likes: MoKaLux, pie

    +1 -1 (+2 / -0 )Share on Facebook
  • piepie Member
    thank you, I am trying gideros UI for the first time! I totally missed that!
    It might do the job but I can't find a way to select the text:

    using viewport:
    I get an error in
    viewport:setPanmode(UI.Viewport.PANMODE.ALWAYS) - attempt to index nil with 'ALWAYS',
    but commenting the line is enough to make it run. It pans perfectly, but I couldn't see a way to tell it if the text should be selectable or not.
    As a side question: does viewport support "bold text/colored" sections/single words?

    using TextField, I can select the text only on windows player with SHIFT+ARROW, but the cursor is stuck at the beginning of the line on every touch or mouse click on the element.

    were you thinking about another UI element?

    Thank you a lot! :)


  • pie go with imgui ;)
    my growING GIDEROS github repositories: https://github.com/mokalux?tab=repositories
  • hgy29hgy29 Maintainer
    @pie, I think you have found a bug in the UI Lib: caret positionning doesn't work well with manually sized textfields.

    Try the code below instead:
    local tf=UI.TextField.new()
    tf:setText(
    "You can select a \e[color=#F00]part\e[color] of this text."
    )
     
    local screen=UI.Screen.new()
     
    tf:setLayoutConstraints({ width=200, })
    screen:ui(tf)

    Likes: MoKaLux, pie

    +1 -1 (+2 / -0 )Share on Facebook
  • piepie Member
    edited October 2023
    hgy29 said:

    @pie, I think you have found a bug in the UI Lib: caret positionning doesn't work well with manually sized textfields.

    Try the code below instead:

    local tf=UI.TextField.new()
    tf:setText(
    "You can select a \e[color=#F00]part\e[color] of this text."
    )
     
    local screen=UI.Screen.new()
     
    tf:setLayoutConstraints({ width=200, })
    screen:ui(tf)
    This indeed works on a single line textfield, but unfortunately it's not exactly what I was looking for because it is still an editable text and I need it "locked".
    If I set a height constraint on the texfield It seems that the same cursor bug as before happens, but I guess this falls under the "manually sized textfield" bug.

    Do you think it might be possible to extend the viewport class and add a "selectable text" flag without too much hassle? Of course I guess that in this scenario it won't be possible to scroll the text without the use of a scrollbar.

    thank you :)
  • hgy29hgy29 Maintainer
    edited October 2023
    Can you try with the patched uitextfield.lua attached and the following test code:
    local tf=UI.TextField.new("",{ flags=FontBase.TLF_TOP|FontBase.TLF_REF_TOP, multiline=true})
    tf:setDimensions(200,500)
    tf:setPosition(50,50)
    tf:setText(
    "You can select a \e[color=#F00]part\e[color] of this text.\nIt should even be possible\nwith multiline texts"
    )
    tf:setFlags({ readonly=true, selectable=true })
     
    local screen=UI.Screen.new()
    screen:ui(tf)
    EDIT: Forum didn't want me to attach a lua file, here it is: http://apps.giderosmobile.com/pre/uitextfield.lua

    Likes: MoKaLux, pie

    +1 -1 (+2 / -0 )Share on Facebook
  • piepie Member
    Thank you a lot @hgy29!
    It almost works:

    the \e[color tag isn't recognized if text is between [[ ]] if there is a quick fix, otherwise I can live without it.


    Is there a way to avoid automatically opening android (and IOS) virtual keyboard?
    This might be useful even for the non readonly textfield: I planned to copy from a first textfield and paste the info into another textfield for validation.

    The popup copy (and cut) buttons raise a
    ui/uitextfield.lua:683: attempt to index nil with 'set'
    ui/uitextfield.lua:676: attempt to index nil with 'set'

    I think that if readonly=true we can directly copy to clipboard without the need for the popup at all: readonly text should not be cut anyway.

    Thank you again

    :)

  • hgy29hgy29 Maintainer
    Yes, as per lua doc, no '\' sequence is recognized in long litterals (bracketted). You can work it around by using another char or sequence and substituting it at run time with gsub, such in:
    mytext=([[
    You can select a [color=#F00]part[color] of this text.
    It should even be possible
    with multiline texts.
    ]]):gsub("%[","\e[")
    Yes, for readonly textfields keyboard shouldn't be requested, I will fix that as well as the cut/paste options. For editable textfields this sounds a bit strange, you could use a readonly textfield (or even a bare UI.Label) and paste the text in it programmatically (with setText) maybe ?

    About the 'attempt to index nil', it seems that UI.Clipboard is nil, while it should have been initialized in uifocus.lua

    Likes: pie

    +1 -1 (+1 / -0 )Share on Facebook
  • hgy29hgy29 Maintainer
    I have modified uitextfield.lua, same link as earlier. I decided to keep the popup even if only 'copy' is available, to give the user a way to cancel his action in case he clicked in the wrong place.
    Also you can add
    UI.Keyboard.NATIVE=false
    UI.Keyboard.VIRTUAL=false
    to your code to disable virtual and native keyboards completely

    Likes: MoKaLux, pie

    +1 -1 (+2 / -0 )Share on Facebook
  • piepie Member
    You really are a genius! Thank you!

    Are you sure that Clipboard is initialized in uifocus.lua? I don't have any occurrence of "Clipboard" outside uitextfield.lua if I search for it with notepad++ "find in files"
    Search "Clipboard" (6 hits in 1 files of 95 searched)
      C:\theApp\assets\ui\uitextfield.lua (6 hits)
    	Line 451: 		UI.Clipboard:get("text/plain",function (res,data,mime)
    	Line 459: 			UI.Clipboard:set(seltext,"text/plain",function (res) end)
    	Line 464: 			UI.Clipboard:set(seltext,"text/plain",function (res) end)
    	Line 677: 					UI.Clipboard:set(seltext,"text/plain",function (res) end)
    	Line 684: 					UI.Clipboard:set(seltext,"text/plain",function (res) end)
    	Line 687: 				UI.Clipboard:get("text/plain",function (res,data,mime)


  • piepie Member
    The uifocus.lua in my gideros installation folder looks like this: it ends on line 174.


    --!NEEDS:uiinit.lua
     
    UI.Focus={ KEYBOARD=1, mode=0 }
    UI.Focus.onFocus={}
    local _weak={ __mode = "kv"}
    setmetatable(UI.Focus.onFocus,_weak)
     
    local function ndispatch(...)
    	local dlist={}
    	for k,v in pairs(UI.Focus.onFocus) do
    		dlist[k]=v
    	end
    	for _,v in pairs(dlist) do
    		local s=nil
    		if v and type(v)=="table" then
    			if v.onFocus then
    				s=v
    				v=s.onFocus
    			else
    				v=v.handler
    				s=v.target
    			end
    		end
    		if v and type(v)=="function" then
    			if s then v(s,...) else v(...) end
    		end
    	end
    end
     
    function UI.Focus:request(w,mode)
      local gained=false
      mode=mode or tonumber(w._flags.focusable) or 0
      if self.focused~=w then
        if self.focused then
          self.focused:setFlags({focused=false})
          if self.focused.unfocus then self.focused:unfocus({ TAG="UI.Focus",reason="FOCUS_CHANGE" }) end
        end
        gained=true
        self.focused=w
        self.mode=mode
        if mode&self.KEYBOARD>0 then
    		self.hasKbd=UI.Keyboard.setKeyboardVisibility(w)
        end
        assert(w and w.setFlags,"Widget must be a descendant of UI.Panel")
        w:setFlags({focused=true})
    	ndispatch(w)
      end
      --print("UI.Focus",w and w.name,"FOCUS?",gained)
      return gained
    end
     
    function UI.Focus:relinquish(w)
      local same=false
      if w and self.focused==w then
        same=true
        self.focused=nil
        if self.mode&self.KEYBOARD>0 then
    		UI.Keyboard.setKeyboardVisibility(nil)
        end
        w:setFlags({focused=false})
        self.mode=0
        if self.hasKbd then
          stage:setPosition(0,0) --Reset stage in case it has been offsetted
          self.hasKbd=nil
        end
    	ndispatch(nil)
      end
      --print("UI.Focus",w and w.name,"UNFOCUS?",same)
      return same
    end
     
    function UI.Focus:area(widget,x,y,w,h,force,anchorx,anchory)
      if self.focused==widget or force then
        UI.dispatchEvent(widget,"FocusArea",x,y,w,h,anchorx,anchory)
        if self.hasKbd then
          stage:setPosition(0,0) --Reset stage in case it has been offsetted
          local gix,giy=widget:localToGlobal(x,y)
          local gax,gay=widget:localToGlobal(x+w,y+h)
          local gsx,gsy,gsw,gsh=application:getLogicalBounds()
    	  gsw-=gsx gsh-=gsy gsh*=(1-self.hasKbd)
          --print(gix,giy,gax,gay,gsw,gsh,self.hasKbd)
          while widget do -- Try to show as complete container chain
    		widget:getLayoutInfo()
            local ix,iy,iw,ih=widget:getBounds(stage,true)
            --print(ix,iy,iw,ih)
            --if iw>gsw or ih>gsh then break end
            if ih>gsh then break end
            gix,giy,gax,gay=ix,iy,ix+iw,iy+ih
            widget=widget:getParent()
          end
    	  if not widget then
    		stage:setPosition(0,0)
    		return
    	  end
          --print(gix,giy,gax,gay) widget:setColor(0xFF0000,1)
          local six,siy=-gix<>-gax,-giy<>-gay
          local sax,say=(gsw-gix)><(gsw-gax),(gsh-giy)><(gsh-gay)
          local cx,cy=stage:getPosition()
          --print(six,siy,sax,say,cx,cy)
          cx=((cx<>six)><sax)><0
          cy=((cy<>siy)><say)><0
          local spx = 0 --NO cx
          local spy = cy
          stage:setPosition(spx,spy)
        end
      end
    end
     
    function UI.Focus:clear()
      if self.focused then
        if self.focused.unfocus then 
          self.focused:unfocus({ TAG="UI.Focus",reason="FOCUS_CLEAR" }) 
        end
    	UI.Focus:relinquish(self.focused)
      end
    end
     
    function UI.Focus:focusRoot(w,mode)
    	while UI.instanceOf(w,UI.Panel) do
    		if w._flags.focusgroup then return w end
    		w=w:getParent()
    	end
    	return UI.Screen.getScreen(w)
    end
     
    function UI.Focus:focusNext(root,mode)
      local t,p,cc={},{root},nil
      while #p>0 do
        local n=table.remove(p,1)
        for i=1,n:getNumChildren() do
          if n._flags and n._flags.focusable and not n._flags.disabled then 
            table.insert(t,n) 
            if self.focused and n==self.focused then cc=#t end
          end
          table.insert(p,n:getChildAt(i))
        end
      end
      if #t==0 then return end
      cc=(cc or 0)%(#t)+1
      return self:request(t[cc],mode)  
    end
     
    function UI.Focus:focusDirection(root,dx,dy,mode,atolerance)
      if not self.focused then return end
      local fx,fy,fw,fh=self.focused:getBounds(root)
      fx=fx+fw/2
      fy=fy+fh/2
      local aref=^>math.atan2(dy,dx)
      local p={root}
      local t,td=nil,1E20
      while #p>0 do
        local n=table.remove(p,1)
        for i=1,n:getNumChildren() do
          if n._flags and n._flags.focusable and not n._flags.disabled and n~=self.focused then 
            local bx,by,bw,bh=n:getBounds(root)
            bx=bx+bw/2
            by=by+bh/2
            local a,d=^>math.atan2(by-fy,bx-fx),((bx-fx)*(bx-fx)+(by-fy)*(by-fy))^0.5
            local ad=math.abs(aref-a)
            if ad<(atolerance or 45) and d<td then
              t=n td=d
            end
          end
          table.insert(p,n:getChildAt(i))
        end
      end
      if t then
        self:request(t,mode)  
      end
    end
     
    function UI.Focus:get()
      return self.focused
    end


    But using your linked file everything works :)

    Thank you
Sign In or Register to comment.