So I managed to successfully convert my Ruby UI::Command
into a Sketchup::Tool
, but I have a question regarding the interaction of my tool with the standard ones provided by SketchUp.
First off, my custom tool begins by presenting a UI::HtmlDialog
to the user where certain parameters have to be inputted. From here the user must select a couple of entities from the 2D view (without relying on the Select tool) and then click a button on the aforementioned UI::HtmlDialog
in order to generate some custom geometry with respect to those inputs. This workflow is fairly intuitive, even though it requires a bit of back and forth between the canvas and the non-modal dialog box.
That being said, my concern is that the user of my extension might want to redraw some of the 2D geometry after invoking my custom tool. For example, a problematic workflow might look like this:
- User activates my custom tool;
- User is unhappy with the geometry and activates the Line tool to make some edits without closing the dialog (which right now is designed to switch focus to the previous tool when closed:
dialog.set_on_closed { Sketchup.active_model.tools.pop_tool }
)
- User clicks back on the dialog hoping to continue where they left off.
However, I don’t think there is a way to reactivate my custom tool just by clicking on the dialog that was previously out of focus. So the user has to close the pop-up and then navigate to the menu bar in order to activate the tool again, which is not quite ideal. The (not so) funny part is that if the dialog is not dismissed and the tool reactivated from the menu bar, then I will end up with two instances of UI::HtmlDialog
…
Anyway, I am trying to determine if:
- it is possible to reactivate a custom tool after clicking on its non-dismissed and out of focus
UI::HtmlDialog
;
- I am doing the right thing keeping the dialog open when the user wants to interact with other tools.
Regarding 2), I tried calling dialog.close
in the Tool#deactivate(view)
method in order to give full focus to the tool that the user intends to select. However, because I put
Sketchup.active_model.tools.pop_tool
in the set_on_closed
callback, this always causes the Select tool to be activated, which is not great at all.
It’s fairly obvious that I’m struggling with designing good UX within SketchUp, so any help will be greatly appreciated!
I think I will just go back to the UI::Command
implementation as I find the Sketchup::Tool
very awkward to work it. Despite giving me the option to “select” points, the tool makes it a lot harder to deal with interactions of standard tools, which someone is very likely to perform after activating my custom tool. With a command, on the other hand, I can keep my dialog open at all times and not worry if the user selects other tools until they are ready to invoke the command. The point selection procedure will not be as user-friendly, though.
I am still interested to know whether point 1) from my post above is feasible or not.
See: Sketchup::Model#select_tool()
If your extension holds a persistent reference for the active model that points at the tool instance then this would be the argument to pass when the dialog is clicked or the document.body
gets the focus.
@dialog.add_action_callback("reactivate_tool") {
model.select_tool(@tool)
}
Trapping when the dialog becomes active can be problematic in CEF using Javascript. See:
What others have done is use the onMouseEnter / onMouseOut on the body or document to fire a Ruby callback that calls @dialog.bring_to_front
which should give the dialog window the focus. (Was bugged until v2021.1 for the Mac.)
Normally a UI::Command
would be created to activate a tool. This allows the command to be added to a menu and used for a toolbar button.
It’s best if a command’s proc only calls a method where the work is done. The reason is that GUI objects cannot be redefined during development but the method can be tweaked and redefined, and the file reloaded. The menu item or toolbar button will always call the latest edition of the method.
1 Like
I’m not using that because of this bug.
I will give onMouseEnter
and onMouseOut
a try later today, but I think I’ve convinced myself that I want to continue with the command approach instead.
Speaking of which, I’m not even creating a UI::Command
object explicitly. My tool-based implementation is roughly along the lines of:
menu.add_item('My Item') {
dialog = UI::HtmlDialog.new(...)
tool = Tool.new(dialog)
Sketchup.active_model.tools.push_tool(tool)
dialog.add_action_callback('click') { |action_context, arg|
main_func(tool, arg)
}
dialog.set_on_closed {
Sketchup.active_model.tools.pop_tool
}
dialog.set_file('html/test.html')
dialog.show
}
Note that I don’t want to include a toolbar button as having an item in the menu bar is just fine.
Those are not useful as they get triggered even if the user hovers over the dialog. I found that focus
and blur
are the events that I should be listening to instead (and they have to be bound to the window
rather than the document
object).
I’ve not been able to get those to fire using CEF dialogs. (See the API Issue linked above.)
I had a look at the Github issue you linked to and also at the code you attached there. I followed the same pattern as you did and everything works just fine under SU 21.1.331 on macOS. Perhaps it doesn’t work in an earlier version if I understand correctly.
In JavaScript:
window.addEventListener('focus', function() {
sketchup.reactivateTool()
});
In Ruby:
my_tool = MyTool.new
Sketchup.active_model.tools.push_tool(my_tool)
dialog.add_action_callback('reactivateTool') { |action_context|
if Sketchup.active_model.tools.active_tool_name != 'RubyTool'
Sketchup.active_model.tools.push_tool(my_tool)
end
}
I don’t like the fact that the custom tool I wrote goes under the rather non-descriptive name of RubyTool
, but I can live with that…
The other small issue I have is that if I select something like the Line tool and go back to my custom tool, the cursor is still renderer as a pencil until I click once on the canvas. How can I force the cursor to be arrow-like right from the start without having to push the Select tool to the stack?
Never mind, I solved that issue as well. Had to find (or rather guess) the id of the select tool, which is 628.
I opened that issue/feature request in February of 2020.
On Windows platform there were bad focus issues before 2021.1. I came back to the issue and posted an update 5 days after v21.1 was released.
The 21.1 release saw the CEF version updated from 64 to 88.
A long standing complaint. We have asked that if a Tool
class defines a #name
getter method that the API use this for Tools.active_tool_name
.
The conditional is fragile as it will be the same for all Ruby tool objects.
If you are using a persistent reference like @tool[Sketchup.active_model]
where @tool
is a hash with model reference keys, then …
# At top of extension submodule:
@tool |= {}
def get_tool
model = Sketchup.active_model
@tool[model]= MyTool.new if @tool[model].nil?
return @tool[model]
end
# Usage:
def push_tool
Sketchup.active_model.tools.push_tool(get_tool())
end
# Define callbacks for HTML dialog.
def define_callbacks(dialog)
dialog.add_action_callback('reactivateTool') { |action_context|
model = Sketchup.active_model
my_tool = get_tool()
tool = model.tools.active_tool # Will be nil if a native tool.
if tool.nil? || tool != my_tool
model.tools.push_tool(my_tool)
end
}
#
# ... other callback definitions ...
#
end
The reason that you want to use a hash with model keys is because on the mac multiple models can be open at the same time. Each model object has it’s own toolstack which should normally have unique tool instances. (More than likely a tool will hold state data specific to the model to which it belongs.)
Also the first time your tool is activated, your code can get and store it’s own tool id.
(I wish that the API did this for us.)
# Inside your tool class:
attr_reader :id
def activate
@id = Sketchup.active_model.tools.active_tool_id if @id.nil?
#
# ... other tool code ...
#
end
Afterward you can test for that integer id.
1 Like