1%%
2%%  wings_tweak.erl --
3%%
4%%     A rewrite of wpc_tweak.erl to add Tweak into the Wings core.
5%%
6%%  Copyright (c) 2009-2011 Richard Jones
7%%
8%%  See the file "license.terms" for information on usage and redistribution
9%%  of this file, and for a DISCLAIMER OF ALL WARRANTIES.
10%%
11%%
12
13-module(wings_tweak).
14
15-export([init/0,command/2, help_msg/0]).
16-export([tweak_event/2,menu/2,tweak_keys_info/0,tweak_disabled_msg/0,
17	 tweak_info_line/0,tweak_magnet_help/0,statusbar/0]).
18
19-export([toggle_draw/1,point_center/3]).
20-export([update_dlist/3,draw/5,get_data/3]).
21
22-export([tweak_keys/0, menu/0, tweak_magnet_menu/0, constraints_menu/0]).  %% For wings_tweak_win only
23
24-export_type([drag/0]).
25
26-define(NEED_OPENGL, 1).
27-define(NEED_ESDL, 1).
28
29-define(L_ALT, 307).
30-define(R_ALT, 308).
31
32-include("wings.hrl").
33-include_lib("e3d/e3d.hrl").
34
35-import(lists,[member/2,foldl/3]).
36
37%%%
38%%% Main Tweak Records
39%%%
40
41-record(tweak,
42	{mode,      % current tweak tool
43	 magnet,    % true|false
44	 mag_type,  % magnet type: Type
45	 mag_rad,   % magnet influence radius
46	 id,        % {Id,Elem} mouse was over when tweak began
47	 sym,       % current magnet radius adjustment hotkey
48         ox,oy,     % original X,Y
49	 x,y,       % current X,Y
50	 cx,cy,     % Calculated X,Y
51         warp,      % true or size limits
52	 clk=none,  % click selection/deselection
53	 st}).      % wings st record (working)
54
55-record(drag,
56	{vs,
57	 pos0,      % Original position.
58	 pos,       % Current position.
59	 pst=none,  % Any data that a specific tweak tool needs stored
60						% temporarily
61	 mag,       % mag record
62	 mm}).      % original|mirror
63
64-record(mag,
65	{orig,      % Orig centre of the selection being moved
66	 vs,        % [{V,Pos,Distance,Influence}]
67	 vtab=[]}). % [{V,Pos}] (latest)
68
69-type drag() :: #drag{}.
70
71%%%
72%%% Set Default Tweak prefs
73%%%
74
75init() ->
76    set_default_tweak_keys(),
77    TweakMagnet = {true,dome,1.0},  %{magnet on, magnet type, magnet radius}
78    wings_pref:set_default(tweak_active,false),
79    wings_pref:set_default(tweak_magnet, TweakMagnet),
80    wings_pref:set_default(tweak_xyz, [false,false,false]),
81    wings_pref:set_default(tweak_axis, screen),
82    wings_pref:set_default(tweak_point, none),
83    wings_pref:set_default(tweak_click_speed, 300000),
84    wings_pref:set_default(tweak_mag_adj_sensitivity, 0.01),
85    wings_pref:set_default(tweak_magnet_color, {0.0, 0.0, 1.0, 0.06}),
86    wings_pref:set_default(tweak_geo_point, none),
87    wings_pref:set_default(tweak_radial, false),
88    wings_pref:set_default(tweak_vector_size, 0.5),
89    wings_pref:set_default(tweak_vector_width, 2.0),
90    wings_pref:set_default(tweak_vector_color, {1.0,0.5,0.0}),
91    wings_pref:set_default(tweak_speed, 0.5), %% control vs speed setting
92    wings_pref:set_default(tweak_axis_toggle, []),
93    wings_pref:set_default(tweak_magnet_influence, true),
94
95    %% Delete Old Prefs
96    wings_pref:delete_value(tweak_draw),
97    wings_pref:delete_value(tweak_help),
98    wings_pref:delete_value(tweak_magnet_colour),
99    wings_pref:delete_value(tweak_sb_clears_constraints),
100    wings_pref:delete_value(tweak_single_click),
101    true.
102
103%%%
104%%% Default Tweak Keys
105%%%
106
107set_default_tweak_keys() ->
108    Cam = wings_pref:get_value(camera_mode),
109    %% Set Default tweak keys according to the camera mode
110    case wings_pref:get_value(tweak_prefs) of
111	{Cam,Prefs0} ->
112	    case check_tweak_prefs(Prefs0) of
113		[] -> set_tweak_keys(Cam);
114		Prefs ->
115		    wings_pref:set_value(tweak_prefs,{Cam,Prefs})
116	    end;
117	_ ->
118	    set_tweak_keys(Cam)
119    end.
120
121set_tweak_keys(Cam) ->
122    %% Set Default tweak keys according to the camera mode
123    TweakKeys = default_tweak_keys(),
124    wings_pref:set_value(tweak_prefs,{Cam,TweakKeys}),
125    wings_wm:dirty(),
126    wings_wm:send({tweak,tweak_palette}, update_palette),
127    TweakKeys.
128
129default_tweak_keys() ->
130    %% This is the format {{MouseButton, {Crtl, Shift, Alt}}, TweakMode}
131    F = false,
132    D = [{{1,{F,F,F}}, move}],
133    orddict:from_list(D).
134
135tweak_keys() ->
136    Cam = wings_pref:get_value(camera_mode),
137    case wings_pref:get_value(tweak_prefs) of
138	{Cam,Keys} -> Keys;
139	_ -> set_tweak_keys(Cam)
140    end.
141
142check_tweak_prefs([{{N,{A,B,C}},Mode}=P|Prefs]) ->
143    Check1 = is_integer(N),
144    Check2 = is_atom(A) andalso is_atom(B) andalso is_atom(C),
145    Check3 = member(Mode, [move,move_normal,scale,scale_uniform,relax,slide]),
146    case Check1 andalso Check2 andalso Check3 of
147	true -> [P|check_tweak_prefs(Prefs)];
148	false -> check_tweak_prefs(Prefs)
149    end;
150check_tweak_prefs([]) -> [].
151
152%%%
153%%% Check for Tweak Events
154%%%
155
156tweak_event(Ev, St) ->
157    case wings_pref:get_value(tweak_active) of
158	true -> tweak_event_handler(Ev, St);
159	false -> next
160    end.
161
162%%% Mouse Buttons
163tweak_event_handler(#mousebutton{button=B,x=X,y=Y,mod=Mod,state=?SDL_PRESSED}, St)
164  when B < 4 ->
165    Cam = wings_pref:get_value(camera_mode),
166    case wings_pref:get_value(tweak_prefs) of
167	{Cam,TweakKeys} ->
168	    Ctrl = Mod band ?CTRL_BITS =/= 0,
169	    Shift = Mod band ?SHIFT_BITS =/= 0,
170	    Alt = Mod band ?ALT_BITS =/= 0,
171	    case orddict:find({B,{Ctrl,Shift,Alt}}, TweakKeys) of
172		{ok, Mode} ->
173		    {Mag,MagType,MagR} = wings_pref:get_value(tweak_magnet),
174                    Warp = case wings_pref:get_value(no_warp, false) of
175                               false -> true;
176                               true -> wings_wm:win_size()
177                           end,
178		    T = #tweak{mode=Mode,warp=Warp,ox=X,oy=Y,x=X,y=Y,
179                               magnet=Mag,mag_type=MagType,mag_rad=MagR,st=St},
180		    handle_tweak_event_1(T);
181		error -> next
182	    end;
183	_ ->
184	    set_tweak_keys(Cam),
185	    next
186    end;
187
188%%% Keyboard hits
189tweak_event_handler(#keyboard{sym=Sym,mod=Mod,state=?SDL_PRESSED}=Ev,St) ->
190    {Mag,MagType,MagR} = wings_pref:get_value(tweak_magnet),
191    case wings_hotkey:event(Ev,St#st{sel=[]}) of
192	{tweak,{tweak_magnet,mag_adjust}} when Mag ->
193	    T = #tweak{magnet=Mag,mag_type=MagType,mag_rad=MagR,sym=Sym,st=St},
194	    magnet_adjust(T);
195	{tweak,{axis_constraint,Axis}} ->
196	    Pressed = wings_pref:get_value(tweak_axis_toggle),
197	    case lists:keymember(Sym, 1, Pressed) of
198		true -> keep;
199		false ->
200		    ReturnAxis = toggle_data(Axis),
201		    wings_pref:set_value(tweak_axis_toggle,[{Sym,ReturnAxis,os:timestamp()}|Pressed]),
202		    wings_io:change_event_handler(?SDL_KEYUP, true),
203		    toggle_axis(Axis),
204		    wings_wm:dirty(),
205		    wings_wm:send({tweak,axis_constraint}, update_palette),
206		    keep
207	    end;
208	next when Mag ->
209	    case magnet_has_hotkey() of
210		true -> next;
211		false ->
212		    case is_altkey_magnet_event(Sym,Mod) of
213			true ->
214			    T = #tweak{magnet=Mag,mag_type=MagType,mag_rad=MagR,sym=Sym,st=St},
215			    magnet_adjust(T);
216			false -> next
217		    end
218	    end;
219	_ -> next
220    end;
221tweak_event_handler(#keyboard{sym=Sym,state=?SDL_RELEASED},_St) ->
222    Pressed0 = wings_pref:get_value(tweak_axis_toggle),
223    case lists:keytake(Sym,1,Pressed0) of
224	{value,{Sym,Axis,PressTime},Pressed} ->
225	    ClickSpeed = wings_pref:get_value(tweak_click_speed),
226	    case timer:now_diff(os:timestamp(), PressTime) > ClickSpeed of
227		true ->
228		    toggle_axis(Axis),
229		    wings_wm:dirty(),
230		    wings_wm:send({tweak,axis_constraint}, update_palette);
231		false -> ok
232	    end,
233	    wings_pref:set_value(tweak_axis_toggle,Pressed),
234	    case Pressed of
235		[] ->
236		    wings_io:change_event_handler(?SDL_KEYUP, false),
237		    keep;
238		_ -> keep
239	    end;
240	false -> keep
241    end;
242
243tweak_event_handler(lost_focus,_) ->
244    wings_pref:set_value(tweak_axis_toggle,[]),
245    wings_io:change_event_handler(?SDL_KEYUP, false),
246    next;
247tweak_event_handler(_,_) ->
248    next.
249
250%%%
251%%% Start Tweak
252%%%
253
254handle_tweak_event_1(#tweak{x=X,y=Y, st=#st{sel=Sel}=St0}=T) ->
255    case wings_pick:do_pick(X,Y,St0) of
256	{add, What, St} when Sel =:= [] ->
257	    from_element_point(X,Y,St0),
258	    tweak_handler_setup(add, What, St, T);
259	{add, What, St} ->
260	    from_element_point(X,Y,St),
261	    tweak_handler_setup(add, What, St, T);
262	{delete, What, _} ->
263	    from_element_point(X,Y,St0),
264	    tweak_handler_setup(delete, What, St0, T);
265	none ->
266	    next
267    end.
268
269tweak_handler_setup(Action, {Id,Elem,_}=What, St, T0) ->
270    IdElem = {Id,[Elem]},
271    T = T0#tweak{id={Action,IdElem},cx=0,cy=0},
272    {seq,push,initiate_tweak_handler(What, St, T)}.
273
274
275%%%
276%%% Initial Event Handler
277%%%
278
279%% Basically we want to wait for a mouse button release which sygnifies
280%% a Pick Event. If anything else happens go into the actual tweak handler.
281initiate_tweak_handler(What, St, T) ->
282    {replace,fun(Ev) ->
283		     handle_initial_event(Ev, What, St, T) end}.
284
285handle_initial_event(redraw, What, St, T) ->
286    wings_draw:refresh_dlists(St),
287    wings:redraw(St),
288    initiate_tweak_handler(What, St, T);
289handle_initial_event(#mousebutton{button=1,state=?SDL_RELEASED}, What, #st{shapes=Shs,sel=Sel0}=St0,
290		     #tweak{id={Action,{Id,[Elem]}},clk=none,x=X,y=Y}=T) ->
291    case wings_io:is_grabbed() of
292	false -> ok;
293	true -> wings_io:ungrab(X,Y)
294    end,
295    St = case Action of
296	     add -> St0;
297	     delete ->
298		 We = gb_trees:get(Id, Shs),
299		 case orddict:find(Id, Sel0) of
300		     _ when ?IS_LIGHT(We) ->
301			 Sel = orddict:erase(Id, Sel0),
302			 St0#st{sel=Sel};
303		     {ok,Sel1} ->
304			 case gb_sets:size(Sel1) of
305			     1 ->
306				 Sel = orddict:erase(Id, Sel0),
307				 St0#st{sel=Sel};
308			     _ ->
309				 Sel2 = gb_sets:delete(Elem, Sel1),
310				 Sel = orddict:store(Id, Sel2, Sel0),
311				 St0#st{sel=Sel}
312			 end;
313		     error ->
314			 Sel = orddict:store(gb_sets:singleton(Elem), Id, Sel0),
315			 St0#st{sel=Sel}
316		 end
317	 end,
318    wings_wm:current_state(St),
319    wings_wm:dirty(),
320    initiate_tweak_handler(What, St, T#tweak{clk={one,os:timestamp()}});
321handle_initial_event(#mousebutton{button=1,x=X0,y=Y0,state=?SDL_PRESSED}=Ev,
322		     _What, St, #tweak{clk={one,Clk},x=X,y=Y,st=TweakSt}) ->
323    case timer:now_diff(os:timestamp(),Clk) < wings_pref:get_value(tweak_click_speed) of
324	true ->
325	    wings_pick:paint_pick(X0, Y0, TweakSt);
326	false ->
327	    Window = wings_wm:this(),
328	    wings_wm:send_after_redraw(Window, Ev#mousebutton{x=X,y=Y}),
329	    wings_wm:later({new_state,St}),
330	    pop
331    end;
332handle_initial_event({new_state,St}, _, _, _) ->
333    %% this is the exiting event from wings_pick after paint_pick/3
334    wings_wm:later({new_state,St}),
335    pop;
336handle_initial_event(#mousemotion{x=X,y=Y}=Ev, What, St,
337		     #tweak{x=OX,y=OY,cx=CX,cy=CY,clk=Clk}=T0) ->
338    DX = X-OX, %since last move X
339    DY = Y-OY, %since last move Y
340    DxOrg = DX+CX, %total X
341    DyOrg = DY+CY, %total Y
342    Total = math:sqrt(DxOrg * DxOrg + DyOrg * DyOrg),
343    T = mouse_warp(X,Y,T0),
344    case Total > 3 of
345	true when Clk =:= none ->
346	    enter_tweak_handler(Ev, What, St, T);
347	true ->
348	    wings_wm:later({new_state,St}),
349	    pop;
350	false ->
351	    initiate_tweak_handler(What, St, T#tweak{cx=DxOrg,cy=DyOrg})
352    end;
353handle_initial_event(Ev, _, St, #tweak{clk={one,_}}) ->
354    wings_wm:send_after_redraw(geom,Ev),
355    wings_wm:later({new_state,St}),
356    pop;
357handle_initial_event(#keyboard{sym=Sym,mod=Mod}=Ev, What, St, #tweak{x=X,y=Y}=T)
358  when Mod band (?ALT_BITS bor ?SHIFT_BITS bor ?CTRL_BITS) =:= 0 ->
359    %% Activate Tweak Camera
360    case wings_camera:tweak_camera_event(Sym, X, Y, St) of
361	next ->
362	    enter_tweak_handler(Ev, What, St, T);
363	Other ->
364	    case wings_io:is_grabbed() of
365		false -> wings_io:grab();
366		true -> ok
367	    end,
368	    Other
369    end;
370handle_initial_event(Ev, What, St, T) ->
371    enter_tweak_handler(Ev, What, St, T).
372
373enter_tweak_handler(Ev, What, St, #tweak{id={Action,_},st=#st{sel=Sel}=St0}=T) ->
374    wings_io:change_event_handler(?SDL_KEYUP, true),
375    wings_wm:grab_focus(),
376    case wings_io:is_grabbed() of
377	true -> ok;
378	false -> wings_io:grab()
379    end,
380    St1 = case wings_pref:get_value(tweak_point) of
381	      _ when Sel =:= [] -> St;
382	      from_element -> St0;
383	      from_cursor -> St0;
384	      _other ->
385		  case wings_pref:get_value(tweak_axis) of
386		      element_normal -> St0;
387		      element_normal_edge -> St0;
388		      _ when Action =:= delete -> St0;
389		      _ -> St
390		  end
391	  end,
392    begin_drag(What, St1, T),
393    do_tweak_0(0, 0, 0, 0, {move,screen}),
394    {replace,fun(Event) -> handle_tweak_drag_event_0(Event, T) end, Ev}.
395
396%%%
397%%% Tweak Event Handlers
398%%%
399
400update_tweak_handler(T) ->
401    case wings_pref:get_value(hide_sel_while_dragging) of
402	true -> ok;
403	false -> wings_draw:update_sel_dlist()
404    end,
405    wings_wm:dirty(),
406    tweak_drag_no_redraw(T).
407
408tweak_drag_no_redraw(T) ->
409    {replace,fun(Ev) -> handle_tweak_drag_event_0(Ev, T) end}.
410
411handle_tweak_drag_event_0(grab_lost, T) ->
412    end_drag(T);
413handle_tweak_drag_event_0(redraw, #tweak{mode=Mode,st=St}=T) ->
414    redraw(St),
415    tweak_keys_info(),
416    info_line(Mode),
417    case statusbar() of
418	[] -> ok;
419	TweakInfo -> wings_io:info(TweakInfo)
420    end,
421    tweak_drag_no_redraw(T);
422
423%%%
424%%% MouseMotion Events
425%%%
426
427handle_tweak_drag_event_0(#mousemotion{}=Ev, #tweak{mode={TwkMode,_}}=T0) ->
428    %% Tweak Modes that can be modified by xyz constraints
429    handle_tweak_drag_event_0(Ev, T0#tweak{mode=TwkMode});
430handle_tweak_drag_event_0(#mousemotion{x=X,y=Y},
431			  #tweak{mode=TweakMode,x=OX,y=OY,cx=CX,cy=CY}=T0) ->
432    Mode =
433	case TweakMode of
434	    move -> actual_mode(TweakMode);
435	    scale -> actual_mode(TweakMode);
436	    move_normal -> actual_mode(TweakMode);
437	    scale_uniform -> actual_mode(TweakMode);
438	    _ -> TweakMode
439	end,
440    DX = X-OX, %since last move X
441    DY = Y-OY, %since last move Y
442    DxOrg = DX+CX, %total X
443    DyOrg = DY+CY, %total Y
444    T1 = mouse_warp(X,Y,T0),
445    do_tweak_0(DX,DY,DxOrg,DyOrg,Mode),
446    T = T1#tweak{mode=Mode,cx=DxOrg,cy=DyOrg},
447    update_tweak_handler(T);
448
449%%%
450%%% Keyboard Events
451%%%
452
453handle_tweak_drag_event_0(#keyboard{sym=Sym,state=?SDL_RELEASED},T) ->
454    Pressed0 = wings_pref:get_value(tweak_axis_toggle),
455    case lists:keytake(Sym,1,Pressed0) of
456	{value,{Sym,Axis,PressTime},Pressed} ->
457	    ClickSpeed = wings_pref:get_value(tweak_click_speed),
458	    case timer:now_diff(os:timestamp(), PressTime) > ClickSpeed of
459		true ->
460		    toggle_axis(Axis),
461		    wings_wm:send({tweak,axis_constraint}, update_palette);
462		false -> ok
463	    end,
464	    wings_pref:set_value(tweak_axis_toggle,Pressed),
465	    update_tweak_handler(T);
466	false -> keep
467    end;
468handle_tweak_drag_event_0(#keyboard{sym=Sym,mod=Mod}=Ev, #tweak{x=OX,y=OY,st=St}=T)
469  when Mod band (?ALT_BITS bor ?SHIFT_BITS bor ?CTRL_BITS) =:= 0 ->
470    %% Activate Tweak Camera
471    case wings_camera:tweak_camera_event(Sym, OX, OY, St) of
472	next -> handle_tweak_drag_event_1(Ev, T);
473	Other -> Other
474    end;
475handle_tweak_drag_event_0(Ev,T) ->
476    handle_tweak_drag_event_1(Ev,T).
477
478handle_tweak_drag_event_1(#keyboard{sym=Sym,mod=Mod}=Ev, #tweak{magnet=Mag,st=St}=T) ->
479    case wings_hotkey:event(Ev, St) of
480	next ->
481	    case  magnet_has_hotkey() of
482		true ->
483		    is_tweak_combo(T);
484		false when Mag->
485		    case is_altkey_magnet_event(Sym,Mod) of
486			true -> tweak_drag_mag_adjust(T#tweak{sym=Sym});
487			false ->
488			    is_tweak_combo(T)
489		    end;
490		false ->
491		    is_tweak_combo(T)
492	    end;
493	{tweak,{tweak_magnet,mag_adjust}} ->
494	    if Mag ->
495		    tweak_drag_mag_adjust(T#tweak{sym=Sym});
496	       true -> keep
497	    end;
498	Action ->
499	    is_tweak_hotkey(Action, T#tweak{sym=Sym})
500    end;
501
502handle_tweak_drag_event_1(Ev,T) ->
503    handle_tweak_drag_event_2(Ev,T).
504
505%%%
506%%% Mouse Button Events
507%%%
508
509handle_tweak_drag_event_2(#mousewheel{}=Ev, #tweak{st=St}) ->
510    case wings_camera:event(Ev, St) of
511	next -> keep;
512	Other -> Other
513    end;
514%% Mouse Button released, so end drag sequence.
515handle_tweak_drag_event_2(#mousebutton{button=B,state=?SDL_RELEASED}, T) when B < 4 ->
516    case  wings_io:get_mouse_state() of
517	{0,_,_} ->
518	    case wings_pref:get_value(tweak_axis_toggle) of
519		[] -> wings_io:change_event_handler(?SDL_KEYUP, false);
520		_ -> ok
521	    end,
522	    end_drag(T);
523	_buttons_still_pressed -> keep
524    end;
525handle_tweak_drag_event_2(_,_) ->
526    keep.
527
528%%%
529%%% Adjust Magnet Radius
530%%%
531
532magnet_adjust(#tweak{st=#st{selmode=body}}) -> next;
533magnet_adjust(#tweak{st=St0}=T0) ->
534    {_,X,Y} = wings_wm:local_mouse_state(),
535    case wings_pick:do_pick(X,Y,St0) of
536	{add, What, St} ->
537	    magnet_handler_setup(What, X, Y, St, T0);
538	{delete, What, _} ->
539	    magnet_handler_setup(What, X, Y, St0, T0);
540	none -> next
541    end.
542
543magnet_handler_setup({Id,Elem,_}=What, X, Y, St, T0) ->
544    wings_io:change_event_handler(?SDL_KEYUP, true),
545    IdElem = {Id,[Elem]},
546    wings_wm:grab_focus(),
547    wings_io:grab(),
548    begin_magnet_adjustment(What, St),
549    tweak_magnet_radius_help(true),
550    T = T0#tweak{id=IdElem,ox=X,oy=Y,x=X,y=Y,cx=0,cy=0},
551    {seq,push,update_magnet_handler(T)}.
552
553%%%
554%%% Magnet Handler
555%%%
556
557update_magnet_handler(T) ->
558    case wings_pref:get_value(hide_sel_while_dragging) of
559	true -> ok;
560	false -> wings_draw:update_sel_dlist()
561    end,
562    wings_wm:dirty(),
563    {replace,fun(Ev) -> handle_magnet_event(Ev, T)end}.
564
565handle_magnet_event(redraw, #tweak{st=St}=T) ->
566    redraw(St),
567    draw_magnet(T),
568    update_magnet_handler(T);
569handle_magnet_event({new_state,St}, T) ->
570    end_magnet_event(T#tweak{st=St});
571handle_magnet_event(#mousemotion{x=X,y=Y},#tweak{x=OX}=T0) ->
572    DX = X-OX, %since last move X
573    T1 = mouse_warp(X,Y,T0),
574    T = adjust_magnet_radius(DX,T1),
575    update_magnet_handler(T);
576%% If something is pressed during magnet radius adjustment, save changes
577%% and begin new event.
578handle_magnet_event(#keyboard{sym=Sym,state=?SDL_RELEASED},#tweak{sym=Sym}=T) ->
579    end_magnet_event(T);
580handle_magnet_event(#keyboard{sym=Sym},#tweak{sym=Sym}) ->
581    keep;
582handle_magnet_event(#keyboard{}=Ev,T) ->
583    end_magnet_event(Ev,T);
584handle_magnet_event(#mousebutton{}=Ev,#tweak{ox=X,oy=Y}=T) ->
585    end_magnet_event(Ev#mousebutton{x=X,y=Y},T);
586handle_magnet_event(#mousemotion{},T) ->
587    end_magnet_event(T);
588handle_magnet_event(Ev,T) ->
589    end_magnet_event(Ev,T).
590
591
592%%%
593%%% Handeler for In-Drag Magnet Radius Adjustments
594%%%
595
596tweak_drag_mag_adjust(#tweak{st=#st{selmode=body}}) -> keep;
597tweak_drag_mag_adjust(#tweak{magnet=false}) -> keep;
598tweak_drag_mag_adjust(#tweak{mode=Mode,cx=CX,cy=CY,x=OX,y=OY}=T0) ->
599    {_,X,Y} = wings_wm:local_mouse_state(),
600    DX = X-OX, %since last move X
601    DY = Y-OY, %since last move Y
602    DxOrg = DX+CX, %total X
603    DyOrg = DY+CY, %total Y
604    T1 = mouse_warp(X, Y, T0),
605    do_tweak_0(DX,DY,DxOrg,DyOrg,Mode),
606    T = T1#tweak{cx=DxOrg,cy=DyOrg},
607    update_in_drag_radius_handler(T).
608
609update_in_drag_radius_handler(T) ->
610    case wings_pref:get_value(hide_sel_while_dragging) of
611	true -> ok;
612	false -> wings_draw:update_sel_dlist()
613    end,
614    wings_wm:dirty(),
615    in_drag_radius_no_redraw(T).
616
617in_drag_radius_no_redraw(T) ->
618    {replace,fun(Ev) ->
619		     handle_in_drag_magnet_ev(Ev, T)end}.
620
621handle_in_drag_magnet_ev(redraw, #tweak{magnet=Mag,st=St}=T) ->
622    redraw(St),
623    tweak_keys_info(),
624    tweak_magnet_radius_help(Mag),
625    draw_magnet(T),
626    in_drag_radius_no_redraw(T);
627handle_in_drag_magnet_ev(#mousemotion{x=X, y=Y},#tweak{x=OX}=T0) ->
628    DX = X-OX, %since last move X
629    T1 = mouse_warp(X,Y,T0),
630    T = in_drag_adjust_magnet_radius(DX,T1),
631    update_in_drag_radius_handler(T);
632handle_in_drag_magnet_ev(#keyboard{sym=Sym,state=?SDL_RELEASED}, #tweak{sym=Sym}=T) ->
633    end_in_drag_mag_event(redraw,T);
634handle_in_drag_magnet_ev(#keyboard{sym=Sym}, #tweak{sym=Sym}) ->
635    keep;
636handle_in_drag_magnet_ev(Ev,T) ->
637    end_in_drag_mag_event(Ev, T).
638
639end_in_drag_mag_event(Ev,#tweak{magnet=Mag, mag_type=MagType, mag_rad=MagR}=T) ->
640    wings_pref:set_value(tweak_magnet, {Mag, MagType, MagR}),
641    handle_tweak_drag_event_0(Ev, T).
642
643%%%
644%%% End Magnet Events
645%%%
646
647end_magnet_event(#tweak{st=St}=T) ->
648    end_magnet_event({new_state,St},T).
649
650end_magnet_event(Ev,#tweak{id=Id}=T) ->
651    wings_io:change_event_handler(?SDL_KEYUP, false),
652    save_magnet_prefs(T),
653    end_magnet_adjust(Id),
654    wings_wm:later(Ev),
655    pop.
656
657%%%
658%%% End of event handlers
659%%%
660
661mouse_warp(_X,_Y,#tweak{warp=true, x=OX,y=OY}=T) ->
662    wings_io:warp(OX,OY),
663    T;
664mouse_warp(X,Y,#tweak{warp={W,H}}=T)
665  when X < 10 orelse Y < 10 orelse X > (W-10) orelse Y > (H-10) ->
666    %% Warp at the window edges
667    Cx = W div 2, Cy = H div 2,
668    wings_io:warp(Cx, Cy),
669    T#tweak{x=Cx,y=Cy};
670mouse_warp(X,Y, T) ->
671    T#tweak{x=X,y=Y}.
672
673redraw(St) ->
674    Render =
675	fun() ->
676		wings_wm:clear_background(),
677		wings_render:render(St)
678	end,
679    wings_io:batch(Render).
680
681%%%
682%%% Magnet Radius Adjustments
683%%%
684
685begin_magnet_adjustment(SelElem, St) ->
686    wings_draw:refresh_dlists(St),
687    wings_dl:map(fun(D, _) ->
688			 begin_magnet_adjustment_fun(D, SelElem)
689		 end, []).
690
691begin_magnet_adjustment_fun(#dlo{src_sel={Mode,Els},src_we=We}=D, SelElem) ->
692    Vs0 = sel_to_vs(Mode, gb_sets:to_list(Els), We),
693    case Vs0 of
694	[] -> D;
695	_ ->
696	    Center = wings_vertex:center(Vs0, We),
697	    MM = case {We,SelElem} of
698		     {#we{id=Id},{Id,_,MM0}} -> MM0;
699		     {_,_} -> original
700		 end,
701	    D#dlo{drag=#drag{pos=Center,mm=MM}}
702    end;
703begin_magnet_adjustment_fun(D, _) -> D.
704
705adjust_magnet_radius(MouseMovement, #tweak{mag_rad=Falloff0}=T0) ->
706    case Falloff0 + MouseMovement * wings_pref:get_value(tweak_mag_adj_sensitivity) of
707        Falloff when Falloff > 0 ->
708            T0#tweak{mag_rad=Falloff};
709        _otherwise -> T0
710    end.
711
712in_drag_adjust_magnet_radius(MouseMovement, #tweak{mag_rad=Falloff0}=T) ->
713    case Falloff0 + MouseMovement * wings_pref:get_value(tweak_mag_adj_sensitivity) of
714        Falloff when Falloff > 0 ->
715            setup_magnet(T#tweak{mag_rad=Falloff});
716        _otherwise -> T
717    end.
718
719end_magnet_adjust({OrigId,El}) ->
720    wings_dl:map(fun(#dlo{src_we=#we{id=Id}}=D, _) ->
721			 if OrigId =:= Id -> show_cursor(El,D); true -> ok end,
722			 D#dlo{vs=none,sel=none,drag=none}
723		 end, []).
724
725%%%
726%%% Begin Drag
727%%%
728
729begin_drag(SelElem, St, T) ->
730    wings_draw:refresh_dlists(St),
731    wings_dl:map(fun(D, _) ->
732			 begin_drag_fun(D, SelElem, St, T)
733		 end, []).
734
735begin_drag_fun(#dlo{src_sel={body,_},src_we=#we{vp=Vtab}=We}=D, _, _, _) ->
736    Vs = wings_util:array_keys(Vtab),
737    Center = wings_vertex:center(Vs, We),
738    Id = e3d_mat:identity(),
739    D#dlo{drag={matrix,Center,Id,e3d_mat:expand(Id)}};
740begin_drag_fun(#dlo{src_sel={Mode,Els},src_we=We}=D0, SelElem, #st{sel=Sel}=St, T) ->
741    Vs0 = sel_to_vs(Mode, gb_sets:to_list(Els), We),
742    case Vs0 of
743	[] -> D0;
744	_ ->
745	    Center = wings_vertex:center(Vs0, We),
746	    {Vs,Magnet,VsDyn} = begin_magnet(T, Vs0, Center, We),
747	    #dlo{src_we=We0}= D = wings_draw:split(D0, Vs, St),
748
749	    L = length(Sel) > 1,
750	    MM = case {We,SelElem} of
751		     {#we{id=Id},{Id,_,_}} when L -> original; %% so at least the shapes
752		     %% drag in the same direction.. if the mirrors are pointed the same too.
753		     {#we{id=Id},{Id,_,MM0}} -> MM0;
754		     {_,_} -> original
755		 end,
756	    NewPst = set_edge_influence(Vs,VsDyn,We0),
757	    D#dlo{src_we=We0#we{pst=NewPst},drag=#drag{vs=Vs0,pos0=Center,pos=Center,mag=Magnet,mm=MM}}
758    end;
759begin_drag_fun(D, _, _, _) -> D.
760
761end_drag(#tweak{mode=Mode,id={_,{OrigId,El}},st=St0}) ->
762    St = wings_dl:map(fun (#dlo{src_we=#we{id=Id}}=D, St1) ->
763			      if OrigId =:= Id -> show_cursor(El,D); true -> ok end,
764			      end_drag(Mode, D, St1)
765		      end, St0),
766    wings_wm:later({new_state,St}),
767    pop.
768
769
770%%%
771%%% End Drag (end tweak event)
772%%%
773
774%% update
775end_drag(update, #dlo{src_sel={Mode,Sel}, src_we=#we{id=Id},drag={matrix,_,Matrix,_}}=D,
776	 #st{shapes=Shs0}=St0) ->
777    We0 = gb_trees:get(Id, Shs0),
778    We = wings_we:transform_vs(Matrix, We0),
779    Shs = gb_trees:update(Id, We, Shs0),
780    St = St0#st{shapes=Shs},
781    {D,St#st{selmode=Mode,sel=[{Id,Sel}]}};
782end_drag(update, #dlo{src_sel={Mode,Sel},src_we=#we{id=Id}}=D0, #st{shapes=Shs0}=St0) ->
783    #dlo{src_we=We} = wings_draw:join(D0),
784    Shs = gb_trees:update(Id, We, Shs0),
785    St = St0#st{shapes=Shs},
786    {D0,St#st{selmode=Mode,sel=[{Id,Sel}]}};
787%% tweak modes
788end_drag(_, #dlo{src_we=#we{id=Id},drag={matrix,_,Matrix,_}}=D,
789	 #st{shapes=Shs0}=St0) ->
790    We0 = gb_trees:get(Id, Shs0),
791    We = wings_we:transform_vs(Matrix, We0),
792    Shs = gb_trees:update(Id, We, Shs0),
793    St = St0#st{shapes=Shs},
794    D1 = D#dlo{src_we=We},
795    D2 = wings_draw:changed_we(D1, D),
796    {D2#dlo{vs=none,sel=none,drag=none},St};
797end_drag(Mode, #dlo{src_sel={_,_},src_we=#we{id=Id}}=D0, #st{shapes=Shs0}=St0) ->
798    case Mode of
799	slide ->
800	    case wings_io:is_key_pressed(?SDLK_F1) of
801		false ->
802		    #dlo{src_we=We}=D = wings_draw:join(D0),
803		    Shs = gb_trees:update(Id, We, Shs0),
804		    St = St0#st{shapes=Shs},
805		    {D#dlo{vs=none,sel=none,drag=none},St};
806		true ->
807		    #dlo{src_we=We} = D = wings_draw:join(D0),
808		    St = case collapse_short_edges(0.0001,We) of
809			     {delete, _} ->
810				 Shs = gb_trees:delete(Id,Shs0),
811				 St0#st{shapes=Shs,sel=[]};
812			     {true, We1} ->
813				 Shs = gb_trees:update(Id, We1, Shs0),
814				 St0#st{shapes=Shs};
815			     {false, We1} ->
816				 Shs = gb_trees:update(Id, We1, Shs0),
817				 St0#st{shapes=Shs, sel=[]}
818			 end,
819		    {D#dlo{vs=none,sel=none,drag=none},St}
820	    end;
821	_ ->
822	    #dlo{src_we=#we{pst=Pst}=We}=D = wings_draw:join(D0),
823	    We0=We#we{pst=remove_pst(Pst)},
824	    Shs = gb_trees:update(Id, We0, Shs0),
825	    St = St0#st{shapes=Shs},
826	    {D#dlo{plugins=[],vs=none,sel=none,drag=none,src_we=We0},St}
827    end;
828end_drag(_, D, St) -> {D, St}.
829
830%%%
831%%% Do Tweak
832%%%
833
834do_tweak_0(DX0, DY0, DxOrg, DyOrg, Mode) ->
835    TweakSpeed = wings_pref:get_value(tweak_speed),
836    DX = DX0 * TweakSpeed,
837    DY = DY0 * TweakSpeed,
838    wings_dl:map(fun
839		     (#dlo{src_we=We}=D, _) when ?IS_LIGHT(We) ->
840			case Mode of
841			    {move,Dir} when Dir =:= normal; Dir =:= element_normal;
842					    Dir =:= default; Dir =:= element_normal_edge ->
843				do_tweak(D, DX, DY, DxOrg, DyOrg, {move,screen});
844			    {move,_} ->
845				do_tweak(D, DX, DY, DxOrg, DyOrg, Mode);
846			    _ ->
847				do_tweak(D, DX, DY, DxOrg, DyOrg, {move,screen})
848			end;
849		     (D, _) ->
850			do_tweak(D, DX, DY, DxOrg, DyOrg, Mode)
851		end, []).
852
853do_tweak(#dlo{drag={matrix,Pos0,Matrix0,_},src_we=#we{id=Id}}=D0,
854	 DX,DY,_,_,Mode) ->
855    Matrices = wings_u:get_matrices(Id, original),
856    {Xs,Ys,Zs} = obj_to_screen(Matrices, Pos0),
857    TweakPos = screen_to_obj(Matrices, {Xs+DX,Ys-DY,Zs}),
858    {Tx,Ty,Tz} = TweakPos,
859    {Px,Py,Pz} = Pos0,
860    Rad = wings_pref:get_value(tweak_radial),
861    Pos = case Mode of
862	      {move,x} -> if Rad -> {Px,Ty,Tz}; true -> {Tx,Py,Pz} end;
863	      {move,y} -> if Rad -> {Tx,Py,Tz}; true -> {Px,Ty,Pz} end;
864	      {move,z} -> if Rad -> {Tx,Ty,Pz}; true -> {Px,Py,Tz} end;
865	      {move,xy} -> if Rad -> {Px,Py,Tz}; true -> {Tx,Ty,Pz} end;
866	      {move,yz} -> if Rad -> {Tx,Py,Pz}; true -> {Px,Ty,Tz} end;
867	      {move,zx} -> if Rad -> {Px,Ty,Pz}; true -> {Tx,Py,Tz} end;
868	      _Other -> TweakPos
869	  end,
870    Move = e3d_vec:sub(Pos, Pos0),
871    Matrix = e3d_mat:mul(e3d_mat:translate(Move), Matrix0),
872    D0#dlo{drag={matrix,Pos,Matrix,e3d_mat:expand(Matrix)}};
873
874do_tweak(#dlo{drag=#drag{vs=Vs,pos=Pos0,pos0=Orig,pst=none,  %% pst =:= none
875			 mag=Mag0,mm=MM}=Drag, src_we=#we{id=Id,mirror=Mir}}=D0,
876	 DX, DY, _DxOrg, _DyOrg, {Scale,Type})
877  when Scale =:= scale; Scale =:= scale_uniform ->
878    %% This is the first time through for Scale ops.
879    %% For default Scaling, figure out the axis of scaling by determining the
880    %% direction of the user's initial mouse motion. Save this PrimeVector to the
881    %% pst field in the #drag record.
882    Matrices = case Mir of
883		   none -> wings_u:get_matrices(Id, original);
884		   _ -> wings_u:get_matrices(Id, MM)
885	       end,
886    {Xs,Ys,Zs} = obj_to_screen(Matrices, Pos0),
887    TweakPos = screen_to_obj(Matrices, {Xs+DX,Ys-DY,Zs}),
888    TweakPointOpType = wings_pref:get_value(tweak_point),
889    {{Axis,ENorm,Point},_} = wings_pref:get_value(tweak_geo_point),
890    Radial = wings_pref:get_value(tweak_radial),
891    {Dir,Pos} = case Type of
892		    x -> {axis,{1.0,0.0,0.0}};
893		    y -> {axis,{0.0,1.0,0.0}};
894		    z -> {axis,{0.0,0.0,1.0}};
895		    xy -> {radial,{0.0,0.0,1.0}};
896		    yz -> {radial,{1.0,0.0,0.0}};
897		    zx -> {radial,{0.0,1.0,0.0}};
898		    normal -> {dir, sel_normal_0(Vs,D0)};
899		    default_axis ->
900			{_,Normal} = wings_pref:get_value(default_axis),
901			{axis,Normal};
902		    element_normal -> {element_normal, Axis};
903		    element_normal_edge -> {element_normal_edge, ENorm};
904		    screen ->
905			case Radial of
906			    true -> {dir, sel_normal_0(Vs,D0)};
907			    false when Scale =:= scale ->
908				{user,TweakPos}; %% This is for Default Scaling
909			    false when Scale =:= scale_uniform ->
910				{uniform,TweakPos}
911			end
912		end,
913    {_,MSX,MSY} = wings_wm:local_mouse_state(), %% Mouse Position
914    {_,YY0} = wings_wm:win_size(wings_wm:this()), %% Window Size global
915
916    %% Cursor Position according to the model coordinates
917    CursorPos = screen_to_obj(Matrices,{float(MSX), float(YY0 - MSY), Zs}),
918    %% vector from where the user starts drag to bbox sel center
919    V1 = e3d_vec:norm_sub(CursorPos,Orig),
920
921    %% scale axis
922    V2 = case e3d_vec:norm_sub(Orig,Pos) of
923	     {0.0,0.0,0.0} -> Pos;
924	     Other -> Other
925	 end,
926
927    %% Flip scale vec depending on the direction
928    %% the user is dragging. To or From the selection center.
929    Dot = e3d_vec:dot(V1, V2),
930    PVec = case  Dot < 0.0 of
931	       true -> e3d_vec:neg(V2);
932	       false -> V2
933	   end,
934
935    PrimeVec = case Dir of
936						% user when TweakPointOpType =:= from_element -> Axis;
937		   user -> PVec;
938		   _other -> Pos
939	       end,
940
941    %% Check for active Point ops
942    VecData = {DistVec,AxisPoint} = case TweakPointOpType of
943					none -> {PVec, {PrimeVec, Orig}};
944					from_cursor -> {e3d_vec:neg(PVec), {PrimeVec, CursorPos}};
945					from_element ->
946					    {e3d_vec:neg(PVec), {PrimeVec, Point}};
947					from_default ->
948					    {DefPoint,_} = wings_pref:get_value(default_axis),
949					    {e3d_vec:neg(PVec), {PrimeVec, DefPoint}}
950				    end,
951
952    Dist = dist_along_vector(Orig, TweakPos, DistVec)/2,
953
954    {Vtab,Mag} = case Dir of
955		     radial -> tweak_scale_radial(Dist, AxisPoint, Mag0);
956		     _ ->
957			 case Radial of
958			     true -> tweak_scale_radial(Dist, AxisPoint, Mag0);
959			     false -> tweak_scale(Dist, AxisPoint, Mag0)
960			 end
961		 end,
962
963    Pst = {Type,Dir,VecData},
964    D = D0#dlo{sel=none,drag=Drag#drag{pos=TweakPos,pst=Pst,mag=Mag}},
965    wings_draw:update_dynamic(D, Vtab);
966
967do_tweak(#dlo{drag=#drag{pos=Pos0,pos0=Orig,pst={Type,Dir,PrimeVec},
968			 mag=Mag0,mm=MM}=Drag, src_we=#we{id=Id,mirror=Mir}}=D0,
969	 DX, DY, _DxOrg, _DyOrg, {Scale,Type})
970  when Scale =:= scale; Scale =:= scale_uniform ->
971    Matrices = case Mir of
972		   none -> wings_u:get_matrices(Id, original);
973		   _ -> wings_u:get_matrices(Id, MM)
974	       end,
975    {Xs,Ys,Zs} = obj_to_screen(Matrices, Pos0),
976    TweakPos = screen_to_obj(Matrices, {Xs+DX,Ys-DY,Zs}),
977    {DistVec,AxisPoint} = PrimeVec,
978    Dist = dist_along_vector(Orig, TweakPos, DistVec)/2,
979    {Vtab,Mag} = case Dir of
980		     uniform -> tweak_scale_uniform(Dist, AxisPoint, Mag0);
981		     radial -> tweak_scale_radial(Dist, AxisPoint, Mag0);
982		     _ ->
983			 case wings_pref:get_value(tweak_radial) of
984			     true -> tweak_scale_radial(Dist, AxisPoint, Mag0);
985			     false -> tweak_scale(Dist, AxisPoint, Mag0)
986			 end
987		 end,
988    D = D0#dlo{sel=none,drag=Drag#drag{pos=TweakPos,mag=Mag}},
989    wings_draw:update_dynamic(D, Vtab);
990do_tweak(#dlo{drag=#drag{vs=Vs,pos=Pos0,mag=Mag0,mm=MM}=Drag,
991	      src_we=#we{id=Id,mirror=Mir}}=D0, DX, DY, _DxOrg, _DyOrg,
992	 {Move,Type}) when Move =:= move; Move =:= move_normal ->
993    Matrices = case Mir of
994		   none -> wings_u:get_matrices(Id, original);
995		   _ -> wings_u:get_matrices(Id, MM)
996	       end,
997    Rad = wings_pref:get_value(tweak_radial),
998    {Xs,Ys,Zs} = obj_to_screen(Matrices, Pos0),
999    TweakPos = screen_to_obj(Matrices, {Xs+DX,Ys-DY,Zs}),
1000    {Tx,Ty,Tz} = TweakPos,
1001    {Px,Py,Pz} = Pos0,
1002    {Vtab,Mag} = case Type of
1003		     x ->
1004			 Pos = if Rad -> {Px,Ty,Tz}; true -> {Tx,Py,Pz} end,
1005			 magnet_tweak(Mag0, Pos);
1006		     y ->
1007			 Pos = if Rad -> {Tx,Py,Tz}; true -> {Px,Ty,Pz} end,
1008			 magnet_tweak(Mag0, Pos);
1009		     z ->
1010			 Pos = if Rad -> {Tx,Ty,Pz}; true -> {Px,Py,Tz} end,
1011			 magnet_tweak(Mag0, Pos);
1012		     xy ->
1013			 Pos = if Rad -> {Px,Py,Tz}; true -> {Tx,Ty,Pz} end,
1014			 magnet_tweak(Mag0, Pos);
1015		     yz ->
1016			 Pos = if Rad -> {Tx,Py,Pz}; true -> {Px,Ty,Tz} end,
1017			 magnet_tweak(Mag0, Pos);
1018		     zx ->
1019			 Pos = if Rad -> {Px,Ty,Pz}; true -> {Tx,Py,Tz} end,
1020			 magnet_tweak(Mag0, Pos);
1021		     normal ->
1022			 Normal = sel_normal_0(Vs, D0),
1023			 Pos = tweak_along_axis(Rad, Normal, Pos0, TweakPos),
1024			 magnet_tweak(Mag0, Pos);
1025		     default_axis ->
1026			 {_,Axis} = wings_pref:get_value(default_axis),
1027			 Pos = tweak_along_axis(Rad, Axis, Pos0, TweakPos),
1028			 magnet_tweak(Mag0, Pos);
1029		     element_normal ->
1030			 {{Axis,_,_},_} = wings_pref:get_value(tweak_geo_point),
1031			 Pos = tweak_along_axis(Rad, Axis, Pos0, TweakPos),
1032			 magnet_tweak(Mag0, Pos);
1033		     element_normal_edge ->
1034			 {{_,Axis,_},_} = wings_pref:get_value(tweak_geo_point),
1035			 Pos = tweak_along_axis(Rad, Axis, Pos0, TweakPos),
1036			 magnet_tweak(Mag0, Pos);
1037		     screen ->
1038			 if Rad; Move =:= move_normal ->
1039				 Normal = sel_normal_0(Vs, D0),
1040				 Pos = tweak_along_axis(Rad, Normal, Pos0, TweakPos),
1041				 magnet_tweak(Mag0, Pos);
1042			    true ->
1043				 Pos = TweakPos,
1044				 magnet_tweak(Mag0, Pos)
1045			 end
1046		 end,
1047    D = D0#dlo{sel=none,drag=Drag#drag{pos=Pos,pst=none,mag=Mag}},
1048    wings_draw:update_dynamic(D, Vtab);
1049do_tweak(#dlo{drag=#drag{pos=Pos0,pos0=Orig,mag=Mag0,mm=MM}=Drag,
1050	      src_we=#we{id=Id,mirror=Mir}=We}=D0, DX, DY, DxOrg, _DyOrg, Mode)
1051  when Mode =:= relax; Mode =:= slide ->
1052    Matrices = case Mir of
1053		   none -> wings_u:get_matrices(Id, original);
1054		   _ -> wings_u:get_matrices(Id, MM)
1055	       end,
1056    {Xs,Ys,Zs} = obj_to_screen(Matrices, Pos0),
1057    TweakPos = screen_to_obj(Matrices, {Xs+DX,Ys-DY,Zs}),
1058    {Vtab,Mag} =
1059	case Mode of
1060	    relax ->
1061		Len0 = abs(DxOrg) / 600,
1062		Len = case Len0 > 1 of
1063			  true -> 1.0;
1064			  false -> Len0
1065		      end,
1066		relax_magnet_tweak_fn(Mag0, We, Len);
1067	    slide ->
1068		magnet_tweak_slide_fn(Mag0, We, Orig, TweakPos)
1069	end,
1070    D = D0#dlo{sel=none,drag=Drag#drag{pos=TweakPos,pst=none,mag=Mag}},
1071    wings_draw:update_dynamic(D, Vtab);
1072do_tweak(#dlo{drag=#drag{}=Drag}=D, _, _, _, _, _) ->
1073    D#dlo{drag=Drag#drag{pst=none}};
1074do_tweak(D, _, _, _, _, _) -> D.
1075
1076%%%
1077%%% Tweak Tool Calculations
1078%%%
1079
1080%%% Scale
1081tweak_scale(Dist, {PVec, Point}, #mag{vs=Vs}=Mag) ->
1082    Vtab = foldl(fun({V, Pos0, Plane, _, Inf}, A) ->
1083			 D = dist_along_vector(Point, Pos0, PVec),
1084			 Pos1 = e3d_vec:add_prod(Pos0, PVec, Inf*D*Dist),
1085			 Pos = mirror_constrain(Plane, Pos1),
1086			 [{V,Pos}|A]
1087		 end, [], Vs),
1088    {Vtab,Mag#mag{vtab=Vtab}}.
1089
1090tweak_scale_radial(Dist, {Norm,Point}, #mag{vs=Vs}=Mag) ->
1091    Vtab = foldl(fun({V, Pos0, Plane, _, Inf}, A) ->
1092			 V1 = e3d_vec:norm_sub(Point, Pos0),
1093			 V2 = e3d_vec:cross(V1,Norm),
1094			 Vec = e3d_vec:norm(e3d_vec:cross(V2,Norm)),
1095			 D = dist_along_vector(Point, Pos0, Vec),
1096			 Pos1 = e3d_vec:add_prod(Pos0, Vec, Inf*D*Dist),
1097			 Pos = mirror_constrain(Plane, Pos1),
1098			 [{V,Pos}|A]
1099		 end, [], Vs),
1100    {Vtab,Mag#mag{vtab=Vtab}}.
1101
1102tweak_scale_uniform(Dist, {_, Point}, #mag{vs=Vs}=Mag) ->
1103    Vtab = foldl(fun({V, Pos0, Plane, _, Inf}, A) ->
1104			 Vec = e3d_vec:sub(Point, Pos0),
1105			 Pos1 = e3d_vec:add_prod(Pos0, Vec, Inf*Dist),
1106			 Pos = mirror_constrain(Plane, Pos1),
1107			 [{V,Pos}|A]
1108		 end, [], Vs),
1109    {Vtab,Mag#mag{vtab=Vtab}}.
1110
1111dist_along_vector(PosA,PosB,Vector) ->
1112    %% Return Distance between PosA and PosB along Vector
1113    {Xa,Ya,Za} = PosA,
1114    {Xb,Yb,Zb} = PosB,
1115    {Vx,Vy,Vz} = e3d_vec:norm(Vector),
1116    Vx*(Xa-Xb)+Vy*(Ya-Yb)+Vz*(Za-Zb).
1117
1118%%% Relax
1119relax_magnet_tweak_fn(#mag{vs=Vs}=Mag,We,Weight) ->
1120    Vtab = foldl(fun({V,P0,Plane,_,1.0}, A) ->
1121			 P1=relax_vec_fn(V,We,P0,Weight),
1122			 P = mirror_constrain(Plane, P1),
1123			 [{V,P}|A];
1124		    ({V,P0,Plane,_,Inf}, A) ->
1125			 P1=relax_vec_fn(V,We,P0,Weight*Inf),
1126			 P = mirror_constrain(Plane, P1),
1127			 [{V,P}|A]
1128		 end, [], Vs),
1129    {Vtab,Mag#mag{vtab=Vtab}}.
1130
1131relax_vec_fn(V, #we{}=We,Pos0,Weight) ->
1132    Vec = relax_vec(V,We),
1133    D = e3d_vec:sub(Vec,Pos0),
1134    e3d_vec:add_prod(Pos0, D, Weight).
1135
1136relax_vec(V, We) ->
1137    case collect_neib_verts_coor(V, We) of
1138	[] ->
1139	    %% Because of hidden faces there may be no neighbouring vertices,
1140	    %% so we default to the position of the vertex itself.
1141	    wings_vertex:pos(V, We);
1142	Cs0 ->
1143	    Cs = [C || C <- Cs0, C =/= undefined],
1144	    if Cs =:= [] -> wings_vertex:pos(V, We);
1145	       true -> e3d_vec:average([wings_vertex:pos(V, We)|Cs])
1146	    end
1147    end.
1148
1149collect_neib_verts_coor(V,We)->
1150    VertList = wings_vertex:fold(fun(_,_,ERec,Acc) ->
1151					 [wings_vertex:other(V,ERec)|Acc]
1152				 end,[],V,We),
1153    foldl(fun(Vert,A) -> [wings_vertex:pos(Vert,We)|A] end,[],VertList).
1154
1155%%% Slide
1156magnet_tweak_slide_fn(#mag{vs=Vs}=Mag, We,Orig,TweakPos) ->
1157    Vtab = foldl(fun({V,P0,Plane,_,Inf}, A) ->
1158			 P1=slide_vec_w(V,P0,Orig,TweakPos,We,Inf,Vs),
1159			 P = mirror_constrain(Plane, P1),
1160			 [{V,P}|A]
1161		 end, [], Vs),
1162    {Vtab,Mag#mag{vtab=Vtab}}.
1163
1164slide_vec_w(V, Vpos, VposS, TweakPosS, We, W,Vs) ->
1165    Dv = e3d_vec:sub(VposS,Vpos),
1166    TweakPos = e3d_vec:sub(TweakPosS, Dv),
1167    Cs0 = collect_neib_verts_coor_vs(V, We, Vs),
1168    Cs1 = [C || C <- Cs0, C =/= undefined],
1169    Cs = sub_pos_from_list(Cs1, Vpos),
1170    TweakPos2=e3d_vec:add(Vpos, e3d_vec:mul(e3d_vec:sub(TweakPos, Vpos), W)),
1171    slide_one_vec(Vpos, TweakPos2, Cs).
1172
1173slide_one_vec(Vpos, TweakPos, PosList) ->
1174    Dpos=e3d_vec:sub(TweakPos,Vpos),
1175    {Dp,_} = foldl(fun
1176		       ({0.0,0.0,0.0},VPW) -> VPW;
1177		       (Vec, {VP,W}) ->
1178			  Vn = e3d_vec:norm(Vec),
1179			  Dotp = e3d_vec:dot(Vn,Dpos),
1180			  if
1181			      Dotp > W, Dotp > 0 ->
1182				  Len = e3d_vec:len(Vec),
1183				  Dotp2 = if
1184					      Dotp > Len -> Len;
1185					      true -> Dotp
1186					  end,
1187				  {e3d_vec:mul(Vn, Dotp2),Dotp};
1188			      true -> {VP,W}
1189			  end
1190		  end,{{0,0,0},0},PosList),
1191    e3d_vec:add(Vpos,Dp).
1192
1193sub_pos_from_list(List,Pos) ->
1194    foldl(fun
1195	      (E,B) -> [e3d_vec:sub(E,Pos)|B] end,[],List).
1196
1197collect_neib_verts_coor_vs(V,We,Vs)->
1198    VertList = wings_vertex:fold(fun(_,_,ERec,Acc) ->
1199					 [wings_vertex:other(V,ERec)|Acc]
1200				 end,[],V,We),
1201    foldl(fun(E,B) -> [get_orig_pos(E,We,Vs)|B] end,[],VertList).
1202
1203get_orig_pos(V,We,Vs)->
1204    Pos=foldl(
1205	  fun({Vert,Coor,_,_,_},P) ->
1206		  if V =:= Vert -> Coor; true-> P end
1207	  end,none,Vs),
1208    case Pos of
1209	none -> wings_vertex:pos(V,We);
1210	_ -> Pos
1211    end.
1212
1213%% scanning over the mesh to collapse short edges
1214collapse_short_edges(Tolerance, #we{es=Etab,vp=Vtab}=We) ->
1215    Short = array:sparse_foldl(
1216	      fun(Edge, #edge{vs=Va,ve=Vb}, A) ->
1217		      case array:get(Va,Vtab) of
1218			  undefined -> A;
1219			  VaPos ->
1220			      case array:get(Vb,Vtab) of
1221				  undefined -> A;
1222				  VbPos ->
1223				      case abs(e3d_vec:dist(VaPos, VbPos)) of
1224					  Dist when Dist < Tolerance -> [Edge|A];
1225					  _Dist -> A
1226				      end
1227			      end
1228		      end
1229	      end, [], Etab),
1230    NothingCollapsed = Short =:= [],
1231    case catch wings_collapse:collapse_edges(Short,We) of
1232        #we{}=We1 ->
1233            {NothingCollapsed, We1};
1234        _ ->
1235            {delete, #we{}}
1236    end.
1237
1238%%% Along Average Normal
1239sel_normal_0(Vs, D) ->
1240    Normals = sel_normal(Vs,D),
1241    e3d_vec:norm(e3d_vec:add(Normals)).
1242
1243sel_normal( _, #dlo{src_we=#we{}=We,src_sel={face,Sel0}}) ->
1244    Faces = gb_sets:to_list(Sel0),
1245    face_normals(Faces,We,[]);
1246sel_normal(Vs,D) ->
1247    [vertex_normal(V, D) || V <- Vs].
1248
1249%%% Radial of Default Axis
1250tweak_along_axis(true, Axis, Pos0, TweakPos) ->
1251    %% constraining by the plane
1252    Dot = e3d_vec:dot(Axis, Axis),
1253    if
1254	Dot =:= 0.0 -> Pos0;
1255	true ->
1256	    T = - e3d_vec:dot(Axis, e3d_vec:sub(TweakPos, Pos0)) / Dot,
1257	    e3d_vec:add_prod(TweakPos, Axis, T)
1258    end;
1259
1260%%% Tweak Along a non-standard Axis
1261tweak_along_axis(false, Axis, Pos0, TweakPos) ->
1262    %% Return the point along the normal closest to TweakPos.
1263    Dot = e3d_vec:dot(Axis, Axis),
1264    if
1265	Dot =:= 0.0 -> Pos0;
1266	true ->
1267	    T = e3d_vec:dot(Axis, e3d_vec:sub(TweakPos, Pos0)) / Dot,
1268	    e3d_vec:add_prod(Pos0, Axis, T)
1269    end.
1270
1271%%%
1272%%% Screen to Object Coordinates
1273%%%
1274
1275obj_to_screen({MVM,PM,VP}, Point) ->
1276    e3d_transform:project(Point, MVM, PM, VP).
1277
1278screen_to_obj({MVM,PM,VP}, Point) ->
1279    e3d_transform:unproject(Point, MVM, PM, VP).
1280
1281sel_to_vs(vertex, Vs, _) -> Vs;
1282sel_to_vs(edge, Es, We) -> wings_vertex:from_edges(Es, We);
1283sel_to_vs(face, Fs, We) -> wings_face:to_vertices(Fs, We).
1284
1285%%%
1286%%% Some Utilities
1287%%%
1288
1289%% vertex_normal(Vertex, DLO) -> UnormalizedNormal
1290%%  Calculate the vertex normal. Will also work for vertices surrounded
1291%%  by one or more hidden faces.
1292vertex_normal(V, D) ->
1293    OrigWe = wings_draw:original_we(D),
1294    FaceNs = [face_normal(F, D) || F <- wings_face:from_vs([V], OrigWe)],
1295    e3d_vec:add(FaceNs).
1296
1297%% face_normal(Face, DLO) -> Normal
1298%%  Calculate the face normal. Will also work for faces that
1299%%  are hidden (including the virtual mirror face).
1300face_normal(Face, #dlo{src_we=#we{vp=Vtab}}=D) ->
1301    #we{vp=OrigVtab} = OrigWe = wings_draw:original_we(D),
1302    Vs = wings_face:vertices_ccw(Face, OrigWe),
1303    VsPos = [vertex_pos(V, Vtab, OrigVtab) || V <- Vs],
1304    e3d_vec:normal(VsPos).
1305
1306face_normals([Face|Fs], We, Normals) ->
1307    N = wings_face:normal(Face, We),
1308    face_normals(Fs, We, [N|Normals]);
1309face_normals([], _We, Normals) ->
1310    Normals.
1311
1312vertex_pos(V, Vtab, OrigVtab) ->
1313    case array:get(V, Vtab) of
1314	undefined -> array:get(V, OrigVtab);
1315	Pos -> Pos
1316    end.
1317
1318magnet_tweak(#mag{orig=Orig,vs=Vs}=Mag, Pos) ->
1319    Vec = e3d_vec:sub(Pos, Orig),
1320    Vtab = foldl(fun({V,P0,Plane,_,1.0}, A) ->
1321			 P1 = e3d_vec:add(P0, Vec),
1322			 P = mirror_constrain(Plane, P1),
1323			 [{V,P}|A];
1324		    ({V,P0,Plane,_,Inf}, A) ->
1325			 P1 = e3d_vec:add_prod(P0, Vec, Inf),
1326			 P = mirror_constrain(Plane, P1),
1327			 [{V,P}|A]
1328		 end, [], Vs),
1329    {Vtab,Mag#mag{vtab=Vtab}}.
1330
1331%% Get center point from closest element to cursor and store it for
1332%% From Element constraints.
1333from_element_point(X ,Y, #st{shapes=Shs}=St0) ->
1334    Stp = St0#st{selmode=face,sel=[],sh=true}, % smart highlight mode
1335    GeomPoint = wings_pick:raw_pick(X,Y,Stp),
1336    {Selmode, _, {IdP, ElemP}} = GeomPoint,
1337    We = gb_trees:get(IdP, Shs),
1338    AxisPoint  = point_center(Selmode, ElemP, We),
1339    wings_pref:set_value(tweak_geo_point, {AxisPoint,GeomPoint}).
1340
1341point_center(vertex, V, #we{vp=Vtab,mirror=Mir}=We) ->
1342    {MirN,Ns} = wings_vertex:fold(fun
1343				      (_, Face, _, {_,A}) when Face =:= Mir ->
1344					 {wings_face:normal(Face, We),A};
1345				      (_, Face, _, {Mb,A}) ->
1346					 {Mb,[wings_face:normal(Face, We)|A]}
1347				 end, {none,[]}, V, We),
1348    Norm = e3d_vec:norm(e3d_vec:add(Ns)),
1349    Normal = case MirN =:= none of
1350		 true -> Norm;
1351		 false ->
1352		     N = e3d_vec:cross(MirN, Norm),
1353		     e3d_vec:cross(N,MirN)
1354	     end,
1355    Pos = array:get(V, Vtab),
1356    {Normal,Normal,Pos};
1357point_center(edge, E, #we{es=Etab,vp=Vtab,mirror=Mir}=We) ->
1358    #edge{vs=Va,ve=Vb,lf=Lf,rf=Rf} = array:get(E, Etab),
1359    PosA = array:get(Va, Vtab),
1360    PosB = array:get(Vb, Vtab),
1361    Pos = e3d_vec:average(PosA, PosB),
1362    EdgeNorm = e3d_vec:norm_sub(PosA,PosB),
1363    FaceNormL = wings_face:normal(Lf, We),
1364    FaceNormR = wings_face:normal(Rf, We),
1365    Normal = case Mir of
1366		 Lf -> N = e3d_vec:cross(FaceNormL,FaceNormR),
1367		       e3d_vec:cross(N,FaceNormL);
1368		 Rf -> N = e3d_vec:cross(FaceNormR,FaceNormL),
1369		       e3d_vec:cross(N,FaceNormR);
1370		 _ -> e3d_vec:average(FaceNormL, FaceNormR)
1371	     end,
1372    {Normal,EdgeNorm,Pos};
1373point_center(face, F, We) ->
1374    Pos = wings_face:center(F, We),
1375    Normal = wings_face:normal(F, We),
1376    {Normal,Normal,Pos}.
1377
1378%%%
1379%%% Magnet Calculations
1380%%%
1381
1382%% Setup magnet in the middle of a tweak op
1383setup_magnet(#tweak{mode=TwkMode, cx=X, cy=Y}=T)
1384  when TwkMode =:= scale;  TwkMode =:= scale_uniform; TwkMode =:= move_normal; TwkMode =:= move ->
1385    wings_dl:map(fun(D, _) ->
1386			 setup_magnet_fun(D, T)
1387		 end, []),
1388    Mode = actual_mode(TwkMode),
1389    do_tweak_0(0, 0, X, Y, Mode),
1390    T;
1391setup_magnet(#tweak{mode=Mode, cx=X, cy=Y}=T) ->
1392    wings_dl:map(fun(D, _) ->
1393			 setup_magnet_fun(D, T)
1394		 end, []),
1395    do_tweak_0(0, 0, X, Y, Mode),
1396    T.
1397
1398setup_magnet_fun(#dlo{src_sel={_,_},drag=#drag{vs=Vs0,pos0=Center}=Drag}=Dl0,
1399		 #tweak{st=St}=T) ->
1400    We = wings_draw:original_we(Dl0),
1401    {Vs,Mag,VsDyn} = begin_magnet(T, Vs0, Center, We),
1402    #dlo{src_we=We0} = Dl = wings_draw:split(Dl0, Vs, St),
1403    NewPst = set_edge_influence(Vs,VsDyn,We0),
1404    Dl#dlo{src_we=We0#we{pst=NewPst},drag=Drag#drag{mag=Mag}};
1405setup_magnet_fun(Dl, _) -> Dl.
1406
1407begin_magnet(#tweak{magnet=false}=T, Vs, Center, We) ->
1408    Mirror = mirror_info(We),
1409    Near = near(Center, Vs, [], Mirror, T, We),
1410    Mag = #mag{orig=Center,vs=Near},
1411    {[Va || {Va,_,_,_,_} <- Near],Mag,[]};
1412begin_magnet(#tweak{magnet=true}=T, Vs, Center, #we{vp=Vtab0}=We) ->
1413    Mirror = mirror_info(We),
1414    Vtab1 = sofs:from_external(array:sparse_to_orddict(Vtab0), [{vertex,info}]),
1415    Vtab2 = sofs:drestriction(Vtab1, sofs:set(Vs, [vertex])),
1416    Vtab = sofs:to_external(Vtab2),
1417    Near = near(Center, Vs, Vtab, Mirror, T, We),
1418    Mag = #mag{orig=Center,vs=Near},
1419    {[Va || {Va,_,_,_,_} <- Near],Mag,[{Va,Inf} || {Va,_,_,_,Inf} <- Near]}.
1420
1421near(Center, Vs, MagVs0, Mirror, #tweak{mag_rad=R,mag_type=Type}, We) ->
1422    RSqr = R*R,
1423    MagVs = minus_locked_vs(MagVs0, We),
1424    M = foldl(fun({V,Pos}, A) ->
1425		      case e3d_vec:dist_sqr(Pos, Center) of
1426			  DSqr when DSqr =< RSqr ->
1427			      D = math:sqrt(DSqr),
1428			      Inf = magnet_type_calc(Type, D, R),
1429			      Matrix = mirror_matrix(V, Mirror),
1430			      [{V,Pos,Matrix,D,Inf}|A];
1431			  _ -> A
1432		      end;
1433		 (_, A) -> A
1434	      end, [], MagVs),
1435    foldl(fun(V, A) ->
1436		  Matrix = mirror_matrix(V, Mirror),
1437		  Pos = wpa:vertex_pos(V, We),
1438		  [{V,Pos,Matrix,0.0,1.0}|A]
1439          end, M, Vs).
1440
1441%%% Magnet Mask
1442minus_locked_vs(MagVs, #we{pst=Pst}) ->
1443    Mask = wings_pref:get_value(magnet_mask_on),
1444    case gb_trees:is_defined(wpc_magnet_mask,Pst) of
1445	true when Mask ->
1446	    LockedVs = gb_sets:to_list(wpc_magnet_mask:get_locked_vs(Pst)),
1447	    remove_masked(LockedVs, MagVs);
1448	_otherwise ->
1449	    MagVs
1450    end.
1451
1452remove_masked([V|LockedVs],MagVs) ->
1453    remove_masked(LockedVs,lists:keydelete(V,1,MagVs));
1454remove_masked([],MagVs) -> MagVs.
1455
1456magnet_type_calc(dome, D, R) when is_float(R) ->
1457    math:sin((R-D)/R*math:pi()/2);
1458magnet_type_calc(straight, D, R) when is_float(R) ->
1459    (R-D)/R;
1460magnet_type_calc(spike, D0, R) when is_float(R) ->
1461    D = (R-D0)/R,
1462    D*D.
1463
1464%%%
1465%%% Draw Magnet
1466%%%
1467
1468draw_magnet(#tweak{st=#st{selmode=body}}) -> ok;
1469draw_magnet(#tweak{magnet=true, mag_rad=R}) ->
1470    wings_dl:fold(fun(D, _) ->
1471			  gl:pushAttrib(?GL_ALL_ATTRIB_BITS),
1472			  gl:disable(?GL_DEPTH_TEST),
1473			  gl:enable(?GL_BLEND),
1474			  gl:blendFunc(?GL_SRC_ALPHA, ?GL_ONE_MINUS_SRC_ALPHA),
1475			  wings_view:load_matrices(false),
1476			  {CR,CG,CB,CA}=wings_pref:get_value(tweak_magnet_color),
1477			  wings_io:set_color({CR,CG,CB,CA}),
1478			  draw_magnet_1(D, R),
1479			  gl:popAttrib()
1480		  end, []);
1481draw_magnet(_) -> ok.
1482
1483draw_magnet_1(#dlo{src_sel={Mode,Els},src_we=We,mirror=Mtx,drag=#drag{mm=Side}}, R) ->
1484    case Side of
1485	mirror -> gl:multMatrixf(Mtx);
1486	original -> ok
1487    end,
1488    Vs = sel_to_vs(Mode, gb_sets:to_list(Els), We),
1489    {X,Y,Z} = wings_vertex:center(Vs, We),
1490    gl:translatef(X, Y, Z),
1491    #{size:=Size, tris:=Tris} = wings_shapes:tri_sphere(#{subd=>4, scale=> R, binary => true}),
1492    wings_vbo:draw(fun(_) -> gl:drawArrays(?GL_TRIANGLES, 0, Size*3) end, Tris);
1493draw_magnet_1(_, _) -> [].
1494
1495mirror_info(#we{mirror=none}) -> {[],identity};
1496mirror_info(#we{mirror=Face}=We) ->
1497    FaceVs = wings_face:vertices_ccw(Face, We),
1498    Flatten = wings_we:mirror_projection(We),
1499    {FaceVs,Flatten}.
1500
1501mirror_matrix(V, {MirrorVs,Flatten}) ->
1502    case member(V, MirrorVs) of
1503	false -> identity;
1504	true -> Flatten
1505    end.
1506
1507mirror_constrain(Matrix, Pos) -> e3d_mat:mul_point(Matrix, Pos).
1508
1509%%%
1510%%% Hotkey and Combo Checking
1511%%%
1512
1513magnet_has_hotkey() ->
1514    Hotkeys = wings_hotkey:matching([tweak_magnet,tweak]),
1515    lists:keymember(mag_adjust,1,Hotkeys).
1516
1517is_altkey_magnet_event(Sym,Mod) ->
1518    case Sym of
1519	?L_ALT -> Mod band (?SHIFT_BITS bor ?CTRL_BITS) =:= 0;
1520	?R_ALT -> Mod band (?SHIFT_BITS bor ?CTRL_BITS) =:= 0;
1521	_ -> false
1522    end.
1523
1524toggle_data(Axis) ->
1525    Tp = wings_pref:get_value(tweak_point),
1526    Ta = wings_pref:get_value(tweak_axis),
1527    Txyz = wings_pref:get_value(tweak_xyz),
1528    Which = Ta =:= screen,
1529    case Axis of
1530	radial -> radial;
1531	from_default -> Tp;
1532	from_cursor -> Tp;
1533	from_element -> Tp;
1534	Axis when Axis =:= x; Axis =:= y; Axis =:= z ->
1535	    if Which -> Axis; true -> Ta end;
1536	Axis ->
1537	    if Which -> Txyz; true -> Ta end
1538    end.
1539
1540is_tweak_hotkey({tweak,Cmd}, #tweak{magnet=Magnet,sym=Sym,st=St0}=T0) ->
1541    case Cmd of
1542	{axis_constraint, Axis} ->
1543	    ReturnAxis = toggle_data(Axis),
1544	    Pressed = wings_pref:get_value(tweak_axis_toggle),
1545	    case lists:keymember(Sym, 1, Pressed) of
1546		true -> keep;
1547		false ->
1548		    Data = [{Sym,ReturnAxis,os:timestamp()}|Pressed],
1549		    wings_pref:set_value(tweak_axis_toggle, Data),
1550		    toggle_axis(Axis),
1551		    wings_wm:send({tweak,axis_constraint}, update_palette),
1552		    St = wings_dl:map(fun (D, _) ->
1553					      update_drag(D, T0)  % used to find mid tweak model data..
1554				      end, St0),
1555		    do_tweak_0(0, 0, 0, 0, {move,screen}),
1556		    update_tweak_handler(T0#tweak{st=St})
1557	    end;
1558	{tweak_magnet, toggle_magnet} ->
1559	    toggle_magnet(),
1560	    {Mag, MagType, _} = wings_pref:get_value(tweak_magnet),
1561	    T = T0#tweak{magnet=Mag, mag_type=MagType},
1562	    wings_wm:send({tweak,tweak_magnet}, update_palette),
1563	    tweak_magnet_help(),
1564	    setup_magnet(T),
1565	    update_tweak_handler(T);
1566	{tweak_magnet,reset_radius} when Magnet->
1567	    Pref = wings_pref:get_value(tweak_magnet),
1568	    wings_pref:set_value(tweak_magnet,setelement(3,Pref,1.0)),
1569	    update_tweak_handler(T0#tweak{mag_rad=1.0});
1570	{tweak_magnet, cycle_magnet} ->
1571	    NewMag = cycle_magnet(),
1572	    set_magnet_type(NewMag),
1573	    T = if NewMag =:= off -> T0#tweak{magnet=false};
1574		   true -> T0#tweak{magnet=true,mag_type=NewMag}
1575		end,
1576	    wings_wm:send({tweak,tweak_magnet}, update_palette),
1577	    tweak_magnet_help(),
1578	    setup_magnet(T),
1579	    update_tweak_handler(T);
1580	{tweak_magnet, MagType} ->
1581	    set_magnet_type(MagType),
1582	    T = T0#tweak{magnet=true, mag_type=MagType},
1583	    wings_wm:send({tweak,tweak_magnet}, update_palette),
1584	    tweak_magnet_help(),
1585	    setup_magnet(T),
1586	    update_tweak_handler(T);
1587	{Mode,1} when Mode =:= move; Mode =:= move_normal; Mode =:= slide;
1588		      Mode =:= scale; Mode =:= scale_uniform; Mode =:= relax ->
1589	    set_tweak_pref(Mode, 1, {false, false, false}),
1590	    wings_wm:send({tweak,tweak_palette}, update_palette),
1591	    is_tweak_combo(T0);
1592	_ ->
1593	    keep
1594    end;
1595
1596is_tweak_hotkey({view,Cmd}, #tweak{st=St0}) when Cmd =/= quick_preview ->
1597    St = wings_dl:map(fun (D, St1) ->
1598			      end_drag(update, D, St1)  % used to find mid tweak model data
1599		      end, St0),
1600    wings_view:command(Cmd, St),
1601    wings_wm:dirty(),
1602    keep;
1603is_tweak_hotkey(_, T) ->
1604    case wings_io:is_key_pressed(?SDLK_SPACE) of
1605	true -> is_tweak_combo(T);
1606	false -> keep
1607    end.
1608
1609is_tweak_combo(#tweak{st=#st{selmode=body}}) ->
1610    keep; % Still haven't add Scale
1611is_tweak_combo(#tweak{mode=Mode,st=St0}=T) ->
1612    {B,X0,Y0} = wings_io:get_mouse_state(),
1613    Ctrl = wings_io:is_modkey_pressed(?CTRL_BITS),
1614    Shift = wings_io:is_modkey_pressed(?SHIFT_BITS),
1615    Alt = wings_io:is_modkey_pressed(?ALT_BITS),
1616    {_,TweakKeys} = wings_pref:get_value(tweak_prefs),
1617    case orddict:find({B,{Ctrl,Shift,Alt}},TweakKeys) of
1618        {ok, Mode} -> keep;
1619        {ok, NewMode} when element(1,Mode) =:= NewMode -> keep;
1620        {ok, NewMode} ->
1621            St = wings_dl:map(fun (D, _) ->
1622				      update_drag(D,T)  % used to find mid tweak model data
1623			      end, St0),
1624            do_tweak_0(0, 0, 0, 0, {move,screen}),
1625	    {X,Y} = wings_wm:screen2local({X0,Y0}),
1626            update_tweak_handler(T#tweak{mode=NewMode,st=St,ox=X,oy=Y,x=X,y=Y,cx=0,cy=0});
1627        _ -> keep
1628    end.
1629
1630update_drag(#dlo{src_sel={Mode,Els},src_we=#we{id=Id},drag=#drag{mm=MM}}=D0,
1631	    #tweak{st=#st{shapes=Shs0}=St0}=T) ->
1632    #dlo{src_we=We}=D1 = wings_draw:join(D0),
1633    Shs = gb_trees:update(Id, We, Shs0),
1634    St = St0#st{shapes=Shs},
1635    Vs0 = sel_to_vs(Mode, gb_sets:to_list(Els), We),
1636    Center = wings_vertex:center(Vs0, We),
1637    {Vs,Magnet,VsDyn} = begin_magnet(T#tweak{st=St}, Vs0, Center, We),
1638    #dlo{src_we=We0}= D = wings_draw:split(D1, Vs, St),
1639    NewPst = set_edge_influence(Vs,VsDyn,We0),
1640    {D#dlo{src_we=We0#we{pst=NewPst},drag=#drag{vs=Vs0,pos0=Center,pos=Center,mag=Magnet,mm=MM}},St};
1641update_drag(D,#tweak{st=St}) -> {D,St}.
1642
1643%%%
1644%%% XYZ Tweak Constraints
1645%%%
1646
1647actual_mode(Mode) ->
1648    axis_constraints(Mode).
1649
1650axis_constraints(Mode) -> %% Mode =:= move; Mode =:= scale
1651    case wings_pref:get_value(tweak_axis) of
1652	screen ->
1653	    TKeys = wings_pref:get_value(tweak_xyz), % Toggled xyz constraints
1654	    case TKeys of
1655		[true,false,false] -> {Mode, x};
1656		[false,true,false] -> {Mode, y};
1657		[false,false,true] -> {Mode, z};
1658		[true,true,false]  -> {Mode, xy};
1659		[false,true,true]  -> {Mode, yz};
1660		[true,false,true]  -> {Mode, zx};
1661		[_,_,_] -> {Mode,screen}
1662	    end;
1663	Other -> {Mode,Other}
1664    end.
1665
1666%%%
1667%%% Show Cursor
1668%%%
1669
1670%% After releasing lmb to conclude drag, unhide the cursor and make sure its
1671%% inside the window at the centre of the selection if possible.
1672show_cursor(_, #dlo{src_we=#we{id=Id}, drag={matrix,Pos,_,_}}) ->
1673    Matrices = wings_u:get_matrices(Id, original),
1674    {X0,Y0,_} = obj_to_screen(Matrices, Pos),
1675    show_cursor_1(X0,Y0);
1676show_cursor(El, #dlo{src_sel={Mode,_},src_we=#we{id=Id}=We,drag=#drag{mm=MM}}) ->
1677    Vs0 = case catch sel_to_vs(Mode, El, We) of
1678	      VsList when is_list(VsList) -> VsList;
1679	      _ -> crash_the_next_check_too
1680	  end,
1681    Center = case catch wings_vertex:center(Vs0, We) of
1682		 {X,Y,Z}=C when C =:= {float(X), float(Y), float(Z)} -> C;
1683		 _ ->
1684		     {{_,_,C},_} = wings_pref:get_value(tweak_geo_point),
1685		     C
1686	     end,
1687    Matrices = wings_u:get_matrices(Id, MM),
1688    {X0,Y0,_} = obj_to_screen(Matrices, Center),
1689    show_cursor_1(X0,Y0);
1690show_cursor(_,_) ->
1691    {{_,_,Center},{_,Mir,{Id,_}}} = wings_pref:get_value(tweak_geo_point),
1692    Matrices = wings_u:get_matrices(Id, Mir),
1693    {X0,Y0,_} = obj_to_screen(Matrices, Center),
1694    show_cursor_1(X0,Y0).
1695
1696show_cursor_1(X0,Y0) ->
1697    {W,H} = wings_wm:win_size(),
1698    {X1,Y1} = {trunc(X0), H - trunc(Y0)},
1699    X = if X1 < 0 -> 20;
1700	   X1 > W -> W-20;
1701	   true -> X1
1702	end,
1703    Y = if Y1 < 0 -> 20;
1704	   Y1 > H -> H-20;
1705	   true -> Y1
1706	end,
1707    wings_wm:release_focus(),
1708    wings_io:ungrab(X,Y).
1709
1710%%%
1711%%% Main Tweak Menu
1712%%%
1713
1714menu(X, Y) ->
1715    Menu = menu(),
1716    wings_menu:popup_menu(X, Y, tweak, Menu).
1717
1718menu() ->
1719    ToggleHelp = ?__(1,"Tweak is a collection of tools for quickly adjusting a mesh."),
1720    Toggle = case wings_pref:get_value(tweak_active) of
1721		 false -> ?__(2,"Enable Tweak");
1722		 true -> ?__(3,"Disable Tweak")
1723	     end,
1724    [{Toggle,toggle_tweak,ToggleHelp},
1725     separator,
1726     {?__(4,"Magnets"),{tweak_magnet, tweak_magnet_menu()}},
1727     {?__(5,"Axis Constraints"),{axis_constraint, constraints_menu()}},
1728     separator,
1729     tweak_menu_item(move,
1730		     ?__(6,"Move selection relative to screen, or constrained to an axis.")),
1731     tweak_menu_item(move_normal,
1732		     ?__(7,"Move selection along average normal.")),
1733     tweak_menu_item(scale,
1734		     ?__(8,"Scale selection relative to screen, or constrained to an axis.")),
1735     tweak_menu_item(scale_uniform,
1736		     ?__(9,"Scale elements uniformly from the selection center.")),
1737     tweak_menu_item(relax,
1738		     ?__(10,"Relax selection toward the average plane of neighbors.")),
1739     tweak_menu_item(slide,
1740		     ?__(11,"Slide elements along adjacent edges.")),
1741     separator,
1742     {?__(13,"Tweak Preferences"),tweak_preferences,
1743      ?__(14,"Preferences for Tweak")}].
1744
1745tweak_menu_item(Mode, Help0) ->
1746    Set = {bold,?__(2,"Set: ")},
1747    Swap = {bold,?__(3,"Swap: ")},
1748    SetHelp = [Set, ?__(4,"Hold modifiers and/or press any mouse button.")],
1749    SwapHelp = [Swap,?__(5,"Press another tool's key binding.")],
1750    DelHelp = [{bold, ?__(6,"Unbind: ")}, button(3)],
1751    Help = wings_msg:join([Help0, SetHelp, SwapHelp, DelHelp]),
1752    {mode(Mode),tweak_menu_fun(Mode),Help, keys_combo(Mode)}.
1753
1754mode({move,_}) ->
1755    ?__(1,"Move");
1756mode(move) ->
1757    ?__(1,"Move");
1758mode({move_normal,_}) ->
1759    ?__(2,"Move Normal");
1760mode(move_normal) ->
1761    ?__(2,"Move Normal");
1762mode({scale,_}) ->
1763    ?__(3,"Scale");
1764mode(scale) ->
1765    ?__(3,"Scale");
1766mode({scale_uniform,_}) ->
1767    ?__(4,"Scale Uniform");
1768mode(scale_uniform) ->
1769    ?__(4,"Scale Uniform");
1770mode(relax) ->
1771    ?__(5,"Relax");
1772mode(slide) ->
1773    ?__(6,"Slide");
1774mode(_) ->
1775    init().
1776
1777tweak_menu_fun(Mode) ->
1778    fun
1779	(B,_) when B < 4 -> {tweak,{Mode,B}};
1780	(_,_) -> ignore
1781		     end.
1782
1783keys_combo(Key) ->
1784    TweakKeys = tweak_keys(),
1785    case lists:keyfind(Key,2,TweakKeys) of
1786	false -> [];
1787	{{Button,Modifiers},_} ->
1788            B = button(Button),
1789            Mod = modifier(Modifiers),
1790            [{hotkey, [Mod,B]}]
1791    end.
1792
1793%%%
1794%%% Constraints Menu
1795%%%
1796
1797constraints_menu() ->
1798    [Fx,Fy,Fz] = wings_pref:get_value(tweak_xyz),
1799    TwAx = wings_pref:get_value(tweak_axis),
1800    TwPt = wings_pref:get_value(tweak_point),
1801
1802    N = normal,
1803    D = default_axis,
1804
1805    NHelp = ?__(1,"Locks movement to the selection's Normal."),
1806    DaHelp = ?__(2,"Locks movement to the Default Axis."),
1807    RHelp = ?__(3,"Locks movement to the Radial of the current Tweak Axis."),
1808    X = wings_s:dir(x),
1809    Y = wings_s:dir(y),
1810    Z = wings_s:dir(z),
1811    Help = ?__(5,"Locks movement to the ~s axis."),
1812    [{X,x,wings_util:format(Help, [X]),crossmark(Fx)},
1813     {Y,y,wings_util:format(Help, [Y]),crossmark(Fy)},
1814     {Z,z,wings_util:format(Help, [Z]),crossmark(Fz)},
1815     separator,
1816     {wings_s:dir(N), N, NHelp, crossmark(TwAx =:= N)},
1817     {axis_string(D), D, DaHelp, crossmark(TwAx =:= D)},
1818     {axis_string(element_normal),element_normal,
1819      ?__(6,"Constrained movement to normal of element marked by Tweak Vector."),
1820      crossmark(TwAx =:= element_normal)},
1821     {axis_string(element_normal_edge),element_normal_edge,
1822      ?__(6,"Constrained movement to normal of element marked by Tweak Vector."),
1823      crossmark(TwAx =:= element_normal_edge)},
1824     separator,
1825     {axis_string(radial), radial, RHelp, crossmark(wings_pref:get_value(tweak_radial))},
1826     separator,
1827     {axis_string(from_cursor),from_cursor,?__(7,"Scale from mouse cursor."),
1828      crossmark(TwPt =:= from_cursor)},
1829     {axis_string(from_element),from_element,?__(8,"Scale from element marked by Tweak Vector."),
1830      crossmark(TwPt =:= from_element)},
1831     {axis_string(from_default),from_default,?__(9,"Scale from Default Point."),
1832      crossmark(TwPt =:= from_default)},
1833     separator,
1834     {?__(10,"Clear Constraints"),clear,?__(11,"Clear all locked axes.")}].
1835
1836%%%
1837%%% Magnet Menu
1838%%%
1839
1840tweak_magnet_menu() ->
1841    {Mag, MagType, _} = wings_pref:get_value(tweak_magnet),
1842    Help = ?__(3,"Tweak magnets are similar to 'soft selection'."),
1843    Toggle = if
1844		 Mag  -> ?__(1,"Disable Magnet");
1845		 true -> ?__(2,"Enable Magnet")
1846	     end,
1847    MagAdj = {?__(5,"Radius Adjust Key"), mag_adjust,
1848	      ?__(6,"Press [Insert] to customize hotkey for adjusting the magnet radius. ") ++
1849		  ?__(7,"Without a hotkey assigned, the magnet radius is adjusted by holding [Alt].")},
1850    Reset = {?__(8,"Reset Radius"), reset_radius,?__(9,"Reset the magnet radius to 1.0")},
1851    Cycle = {?__(10,"Next Magnet Type"), cycle_magnet, ?__(11,"Can be hotkeyed to cycle through the magnets.")},
1852    Dome = {magnet_type(dome), dome, mag_thelp(dome),
1853            crossmark({dome, MagType})},
1854    Straight = {magnet_type(straight), straight, mag_thelp(straight),
1855                crossmark({straight, MagType})},
1856    Spike = {magnet_type(spike), spike, mag_thelp(spike),
1857	     crossmark({spike, MagType})},
1858    [{Toggle, toggle_magnet, Help}, separator,
1859     Dome, Straight, Spike, separator,
1860     Reset, separator,
1861     Cycle, MagAdj].
1862
1863magnet_type(dome) -> ?__(1,"Dome");
1864magnet_type(straight) -> ?__(2,"Straight");
1865magnet_type(spike) -> ?__(3,"Spike").
1866
1867mag_thelp(dome) -> ?__(1,"This magnet pulls and pushes geometry with an even and rounded effect.");
1868mag_thelp(straight) -> ?__(2,"This magnet pulls and pushes geometry with a straight effect.");
1869mag_thelp(spike) -> ?__(3,"This magnet pulls and pushes geometry out to a sharp point.").
1870
1871cycle_magnet() ->
1872    {MagBool,MagType,_} = wings_pref:get_value(tweak_magnet),
1873    case MagType of
1874	dome -> straight;
1875	straight -> spike;
1876	spike when MagBool =:= false -> dome;
1877	spike -> off
1878    end.
1879
1880crossmark({MagType, MagType}) -> [{crossmark, true}];
1881crossmark({_, MagType}) when is_atom(MagType)-> [{crossmark, false}];
1882crossmark("none") -> [{crossmark, false}];
1883crossmark(false) -> [{crossmark, false}];
1884crossmark(_) -> [{crossmark, true}].
1885
1886
1887%%%
1888%%% Tweak Commands
1889%%%
1890
1891command(toggle_tweak, St) ->
1892    Pref = wings_pref:get_value(tweak_active),
1893    wings_pref:set_value(tweak_active, not Pref),
1894    wings_wm:send({tweak,tweak_palette}, update_palette),
1895    case wings_wm:is_geom() of
1896	true -> wings:info_line();
1897	false -> ok
1898    end,
1899    St;
1900command({tweak_magnet,toggle_magnet}, St) ->
1901    toggle_magnet(),
1902    case wings_wm:is_geom() of
1903	true -> wings:info_line();
1904	false -> ok
1905    end,
1906    wings_wm:send({tweak,tweak_magnet}, update_palette),
1907    St;
1908command({tweak_magnet,reset_radius}, St) ->
1909    Pref = wings_pref:get_value(tweak_magnet),
1910    wings_pref:set_value(tweak_magnet,setelement(3,Pref,1.0)),
1911    St;
1912command({tweak_magnet,cycle_magnet}, St) ->
1913    NewMag = cycle_magnet(),
1914    set_magnet_type(NewMag),
1915    wings_wm:send({tweak,tweak_magnet}, update_palette),
1916    case wings_wm:is_geom() of
1917	true -> wings:info_line();
1918	false -> ok
1919    end,
1920    St;
1921command({tweak_magnet,mag_adjust}, St) ->
1922    St;
1923command({tweak_magnet,MagType}, St) ->
1924    set_magnet_type(MagType),
1925    wings_wm:send({tweak,tweak_magnet},update_palette),
1926    case wings_wm:is_geom() of
1927	true -> wings:info_line();
1928	false -> ok
1929    end,
1930    St;
1931command(tweak_preferences, St) ->
1932    tweak_preferences_dialog(St);
1933command({axis_constraint, Axis}, St) ->
1934    toggle_axis(Axis),
1935    wings_wm:send({tweak,axis_constraint},update_palette),
1936    St;
1937command({set_tweak_pref,Mode,B,Mods}, St) when B =< 3->
1938    set_tweak_pref(Mode, B, Mods),
1939    wings_wm:send({tweak,tweak_palette},update_palette),
1940    St;
1941command({tweak_palette, Cmd}, St) ->
1942    command(Cmd, St);
1943command({Mode,B}, St) when B =< 3->
1944    Ctrl = wx_misc:getKeyState(?WXK_CONTROL),
1945    Shift = wx_misc:getKeyState(?WXK_SHIFT),
1946    Alt = wx_misc:getKeyState(?WXK_ALT),
1947    set_tweak_pref(Mode, B, {Ctrl, Shift, Alt}),
1948    wings_wm:send({tweak,tweak_palette},update_palette),
1949    St;
1950command(Mode, St) when Mode =:= move; Mode =:= move_normal; Mode =:= scale;
1951		       Mode =:= scale_uniform; Mode =:= slide; Mode =:= relax ->
1952    set_tweak_pref(Mode, 1, {false, false, false}),
1953    wings_wm:send({tweak,tweak_palette},update_palette),
1954    St;
1955command(_What,_) ->
1956    io:format("Skipping ~p~n",[_What]),
1957    next.
1958
1959%%%
1960%%% Process Commands
1961%%%
1962
1963%% Delete Tweak bind key
1964set_tweak_pref(Mode, 3, {false,false,false}) ->
1965    Cam = wings_pref:get_value(camera_mode),
1966    TweakKeys = case wings_pref:get_value(tweak_prefs) of
1967		    {Cam,TweakKeys0} ->
1968			lists:keydelete(Mode,2,TweakKeys0);
1969		    _ -> set_tweak_keys(Cam)
1970		end,
1971    wings_pref:set_value(tweak_prefs, {Cam,TweakKeys});
1972%% Set new Tweak bind key or swap functions if the bind keys already exist
1973set_tweak_pref(Mode, B, Modifiers) ->
1974    Cam = wings_pref:get_value(camera_mode),
1975    exceptions(Cam,B,Modifiers),
1976    Keys1 = tweak_keys(),
1977    Keys = case lists:keyfind(Mode,2,Keys1) of
1978	       false ->
1979		   orddict:store({B,Modifiers},Mode,Keys1);
1980	       {OldKey,Mode} ->
1981		   Keys2 = case orddict:find({B,Modifiers},Keys1) of
1982			       {ok, OldMode} -> orddict:store(OldKey,OldMode,Keys1);
1983			       error -> lists:keydelete(Mode,2,Keys1)
1984			   end,
1985		   orddict:store({B,Modifiers},Mode,Keys2)
1986	   end,
1987    wings_pref:set_value(tweak_prefs, {Cam,Keys}).
1988
1989%% A bind keys that conflict with either menus or camera buttons are listed here
1990exceptions(wings_cam,2,{false,false,false}) -> cam_conflict();
1991exceptions(mirai,2,{false,false,false}) -> cam_conflict();
1992exceptions(nendo,2,{false,false,false}) -> cam_conflict();
1993exceptions(maya,_,{false,false,true}) -> cam_conflict();
1994exceptions(tds,2,{false,false,false}) -> cam_conflict();
1995exceptions(tds,2,{false,false,true}) -> cam_conflict();
1996exceptions(tds,2,{true,false,true}) -> cam_conflict();
1997exceptions(blender,2,{false,false,false}) -> cam_conflict();
1998exceptions(blender,2,{false,true,false}) -> cam_conflict();
1999exceptions(blender,2,{true,false,false}) -> cam_conflict();
2000exceptions(mb,1,{true,true,false}) -> cam_conflict();
2001exceptions(mb,1,{true,false,false}) -> cam_conflict();
2002exceptions(mb,1,{false,true,false}) -> cam_conflict();
2003exceptions(sketchup,2,{false,false,false}) -> cam_conflict();
2004exceptions(sketchup,2,{false,true,false}) -> cam_conflict();
2005exceptions(_,3,{true,false,false}) -> menu_conflict();
2006exceptions(_,_,_) -> ok.
2007
2008-spec menu_conflict() -> no_return().
2009menu_conflict() ->
2010    wings_u:error_msg(?__(1,"Key combo was not assigned.\n
2011    Those keys would conflict with the right click Tweak menu.")).
2012
2013-spec cam_conflict() -> no_return().
2014cam_conflict() ->
2015    wings_u:error_msg(?__(1,"Key combo was not assigned.\n
2016    That key combination would conflict with the Camera buttons")).
2017
2018%%%
2019%%% Toggle Draw Routines
2020%%%
2021
2022%% For things like Tweak Vectors. Using props should work per window.
2023toggle_draw(Bool) ->
2024				 wings_wm:set_prop(tweak_draw, Bool).
2025
2026%%%
2027%%% Toggle Axes
2028%%%
2029
2030toggle_axis(Axis) ->
2031    toggle_axis_1(Axis).
2032
2033toggle_axis_1([X,Y,Z]) ->
2034    wings_pref:set_value(tweak_axis, screen),
2035    wings_pref:set_value(tweak_xyz, [X,Y,Z]);
2036toggle_axis_1(clear) ->
2037    wings_pref:set_value(tweak_xyz,[false,false,false]),
2038    wings_pref:set_value(tweak_point,none),
2039    wings_pref:set_value(tweak_axis, screen),
2040    wings_pref:set_value(tweak_radial, false);
2041toggle_axis_1(P) when P =:= from_cursor; P =:= from_element; P =:= from_default; P =:= none ->
2042    Pref = case wings_pref:get_value(tweak_point) of
2043	       P -> none;
2044	       _ -> P
2045	   end,
2046    wings_pref:set_value(tweak_point,Pref);
2047toggle_axis_1(Axis) when Axis =:= x; Axis =:= y; Axis =:= z->
2048    case wings_pref:get_value(tweak_xyz) of
2049	[X,Y,Z] ->
2050	    NewPref = case Axis of
2051			  x -> [not X, Y, Z];
2052			  y -> [X, not Y, Z];
2053			  z -> [X, Y, not Z]
2054		      end,
2055	    wings_pref:set_value(tweak_axis, screen),
2056	    wings_pref:set_value(tweak_xyz, NewPref);
2057	_ -> ok
2058    end;
2059toggle_axis_1(radial) ->
2060    case wings_pref:get_value(tweak_radial) of
2061	true ->
2062	    wings_pref:set_value(tweak_radial, false);
2063	false ->
2064	    wings_pref:set_value(tweak_radial, true),
2065	    case wings_pref:get_value(tweak_axis) of
2066		uniform ->
2067		    wings_pref:set_value(tweak_axis, screen);
2068		_ -> ok
2069	    end
2070    end;
2071toggle_axis_1(Axis) ->
2072    wings_pref:set_value(tweak_xyz,[false,false,false]),
2073    case wings_pref:get_value(tweak_axis) of
2074	Axis ->
2075	    wings_pref:set_value(tweak_axis,screen);
2076	_otherwise ->
2077	    wings_pref:set_value(tweak_axis, Axis)
2078    end.
2079
2080%%%
2081%%% Toggle Magnets
2082%%%
2083
2084toggle_magnet() ->
2085    {Mag, MagType, MagRad} = wings_pref:get_value(tweak_magnet),
2086    wings_pref:set_value(tweak_magnet,{not Mag, MagType, MagRad}).
2087
2088set_magnet_type(off) -> %% for cycling magnets
2089    {_, MagType, MagRad} = wings_pref:get_value(tweak_magnet),
2090    wings_pref:set_value(tweak_magnet,{false, MagType, MagRad});
2091set_magnet_type(MagType) ->
2092    {_, _, MagRad} = wings_pref:get_value(tweak_magnet),
2093    wings_pref:set_value(tweak_magnet,{true, MagType, MagRad}).
2094
2095save_magnet_prefs(#tweak{magnet=Mag, mag_type=MT, mag_rad=MagR}) ->
2096    wings_pref:set_value(tweak_magnet, {Mag, MT, MagR}).
2097
2098%%%
2099%%% Tweak Preference Dialog
2100%%%
2101
2102tweak_preferences_dialog(St) ->
2103    ClkSpd = wings_pref:get_value(tweak_click_speed)/100000,
2104    MagAdj = wings_pref:get_value(tweak_mag_adj_sensitivity)*100.0,
2105    VecSize = wings_pref:get_value(tweak_vector_size),
2106    VecWidth = wings_pref:get_value(tweak_vector_width),
2107    TweakSpeed = wings_pref:get_value(tweak_speed),
2108    Menu = [{vframe,
2109	     [{hframe,[{slider,{text,ClkSpd,[{key,tweak_click_speed},{range,{1.0,5.0}}]}}],
2110	       [{title,?__(1,"Click Speed for Select/Deselect")}]},
2111
2112	      {vframe,[{hframe,[{slider,{text,TweakSpeed,[{key,tweak_speed},{range,{0.01,1.0}}]}}]},
2113		       {label,?__(2,"Lower to increase control or raise to increase speed.")}],
2114	       [{title,?__(3,"Tweak Speed (Drag Response)")}]},
2115
2116	      {hframe,[{slider,{text,MagAdj,[{key,tweak_mag_adj_sensitivity},{range,{0.1,2.0}}]}}],
2117	       [{title,?__(4,"Magnet Radius Adjustment Sensitivity")}]},
2118
2119	      {label_column,[{color,?__(5,"Magnet Radius Display Color"),tweak_magnet_color}]},
2120	      {?__(11,"Show Magnet influence"),tweak_magnet_influence},
2121	      {hframe,
2122	       [{vframe,[{label,?__(6,"Length")},
2123			 {label,?__(7,"Width")},
2124			 {label,?__(8,"Color")}]},
2125		{vframe,[{text,VecSize,[{key,tweak_vector_size},{range,{0.1,10.0}}]},
2126			 {text,VecWidth,[{key,tweak_vector_width},{range,{1.0,10.0}}]},
2127			 {color,tweak_vector_color}]}],
2128	       [{title,?__(9,"Tweak Vector")}]}
2129	     ]}],
2130    PrefQs = [{Lbl, make_query(Ps)} || {Lbl, Ps} <- Menu],
2131    wings_dialog:dialog(?__(10,"Tweak Preferences"), PrefQs,
2132			fun(Result) -> set_values(Result), St end).
2133
2134make_query([_|_]=List)  ->
2135    [make_query(El) || El <- List];
2136make_query({[_|_]=Str,Key}) ->
2137    case wings_pref:get_value(Key) of
2138	Def when Def =:= true; Def =:= false ->
2139	    {Str,Def,[{key,Key}]};
2140	Def ->
2141	    {Str,{text,Def,[{key,Key}]}}
2142    end;
2143make_query({color,Key}) ->
2144    Def = wings_pref:get_value(Key),
2145    {color,Def,[{key,Key}]};
2146make_query({color,[_|_]=Str,Key}) ->
2147    Def = wings_pref:get_value(Key),
2148    {Str,{color,Def,[{key,Key}]}};
2149make_query(Tuple) when is_tuple(Tuple) ->
2150    list_to_tuple([make_query(El) || El <- tuple_to_list(Tuple)]);
2151make_query(Other) -> Other.
2152
2153set_values([{tweak_click_speed = Key, Value}|Result]) ->
2154    wings_pref:set_value(Key, Value*100000),
2155    set_values(Result);
2156set_values([{tweak_mag_adj_sensitivity = Key, Value}|Result]) ->
2157    wings_pref:set_value(Key, Value/100.0),
2158    set_values(Result);
2159
2160set_values([{Key,Value}|Result]) ->
2161    wings_pref:set_value(Key, Value),
2162    set_values(Result);
2163set_values([]) -> ok.
2164
2165%%%
2166%%% Tweak Mode Active Info Line
2167%%%
2168
2169tweak_info_line() ->
2170    M1 = ?__(1,"L: Click Select"),
2171    M2 = ?__(2,"LL: Paint Select"),
2172    M3 = wings_msg:button_format([], [], ?__(4,"Menu")),
2173    M4 = wings_msg:rmb_format(?__(3,"Tweak Menu")),
2174    Message = wings_msg:join([M1,M2,M3,M4]),
2175    wings_wm:message(Message).
2176
2177%% Go through list of user defined keys and tweak tool, and build an info line
2178tweak_keys_info() ->
2179    This = wings_wm:is_geom(),
2180    case wings_wm:lookup_prop(tweak_draw) of
2181	{value,true} when This ->
2182	    case wings_pref:get_value(tweak_active) of
2183		true ->
2184		    Keys = tweak_keys(),
2185		    Msg = compose_info_line(Keys),
2186		    draw_tweak_info_line(Msg);
2187		_ -> ok
2188	    end;
2189	_ -> ok
2190    end.
2191
2192compose_info_line([{{Button,Modifiers},Mode}|D]) ->
2193    Mod = modifier(Modifiers),
2194    B = button(Button) ++ ": ",
2195    case mode(Mode) of
2196	true -> compose_info_line(D);
2197	M ->
2198	    Message = [Mod,B,M],
2199	    wings_msg:join([Message,compose_info_line(D)])
2200    end;
2201compose_info_line([]) -> [].
2202
2203button(1) -> wings_s:lmb();
2204button(2) -> wings_s:mmb();
2205button(3) -> wings_s:rmb().
2206
2207modifier({Ctrl,Shift,Alt}) ->
2208    C = if Ctrl -> [wings_s:key(ctrl),"+"]; true -> [] end,
2209    S = if Shift -> [wings_s:key(shift),"+"]; true -> [] end,
2210    A = if Alt -> [wings_s:key(alt),"+"]; true -> [] end,
2211    [C,S,A].
2212
2213draw_tweak_info_line(Msg) ->
2214    {_,H} = wings_wm:win_size(),
2215    wings_io:info(0, H-?LINE_HEIGHT-3, wings_msg:join([Msg])).
2216
2217%%%
2218%%% XYZ Constraints Info line while Tweaking
2219%%%
2220
2221statusbar() ->
2222    Active = wings_pref:get_value(tweak_active),
2223    Draw = wings_wm:lookup_prop(tweak_draw) =:= {value,true},
2224    Geom = wings_wm:is_geom(),
2225    case Active andalso Draw andalso Geom of
2226	true ->
2227	    TweakAxis = wings_pref:get_value(tweak_axis),
2228	    TweakPoint = wings_pref:get_value(tweak_point),
2229	    RadialAxis = wings_pref:get_value(tweak_radial),
2230	    Hotkeys = wings_hotkey:matching([axis_constraint,tweak]),
2231	    TKeys = wings_pref:get_value(tweak_xyz),
2232	    XYZHelp = xyzkey_help(TKeys, Hotkeys),
2233	    Normal = toggle_bold(TweakAxis, normal, Hotkeys),
2234	    DefaultAxis = toggle_bold(TweakAxis, default_axis, Hotkeys),
2235	    ElementNormal = toggle_bold(TweakAxis, element_normal, Hotkeys),
2236	    ElementNormalE = toggle_bold(TweakAxis, element_normal_edge, Hotkeys),
2237	    Radial = toggle_bold(RadialAxis, radial, Hotkeys),
2238	    FromCursor = toggle_bold(TweakPoint, from_cursor, Hotkeys),
2239	    FromElement = toggle_bold(TweakPoint, from_element, Hotkeys),
2240	    DefaultPoint = toggle_bold(TweakPoint, from_default, Hotkeys),
2241	    Help = wings_msg:join([XYZHelp,Normal,DefaultAxis,ElementNormal,
2242				   ElementNormalE,Radial,
2243				   FromCursor,FromElement,DefaultPoint]),
2244	    wings_msg:join([Help]);
2245	false -> []
2246    end.
2247
2248toggle_bold(Axis0, Axis, Hotkeys) ->
2249    case matching_hotkey(Axis, Hotkeys) of
2250	[] -> [];
2251	{none,Name} when Axis0 =:= from_default andalso Axis0 =:= Axis;
2252			 Axis0 =:= from_cursor andalso Axis0 =:= Axis;
2253			 Axis0 =:= from_element andalso Axis0 =:= Axis ->
2254	    [?__(1,"Active Point: "),{bold,Name}];
2255	{none,Name} when Axis0 =:= Axis; Axis0 ->
2256	    [?__(2,"Active Axis: "),{bold,Name}];
2257	Keys when Axis0 =:= Axis; Axis0 ->
2258	    [wings_hotkey:format_hotkey(Keys, pretty),": ", {bold,axis_string(Axis)}];
2259	{none,_} ->
2260	    [];
2261	Keys ->
2262	    [wings_hotkey:format_hotkey(Keys, pretty),": ", axis_string(Axis)]
2263    end.
2264
2265xyzkey_help({_,_}, _) -> [];
2266xyzkey_help([X,Y,Z], Hotkeys) ->
2267    XStr = axis_string(x),
2268    YStr = axis_string(y),
2269    ZStr = axis_string(z),
2270    StrX = if X -> [{bold,XStr}]; true -> XStr end,
2271    StrY = if Y -> [{bold,YStr}]; true -> YStr end,
2272    StrZ = if Z -> [{bold,ZStr}]; true -> ZStr end,
2273    XKeys = case matching_hotkey(x, Hotkeys) of
2274		{none,_} when X -> [?__(1,"Active Axis: "),StrX];
2275		{none,_} -> [];
2276		Xkey -> ["[",wings_hotkey:format_hotkey(Xkey, pretty),"]: ",StrX]
2277	    end,
2278    YKeys = case matching_hotkey(y, Hotkeys) of
2279		{none,_} when Y -> [?__(1,"Active Axis: "),StrY];
2280		{none,_} -> [];
2281		Ykey -> ["[",wings_hotkey:format_hotkey(Ykey, pretty),"]: ",StrY]
2282	    end,
2283    ZKeys = case matching_hotkey(z, Hotkeys) of
2284		{none,_} when Z -> [?__(1,"Active Axis: "),StrZ];
2285		{none,_} -> [];
2286		Zkey -> ["[",wings_hotkey:format_hotkey(Zkey, pretty),"]: ",StrZ]
2287	    end,
2288    wings_msg:join([XKeys,YKeys,ZKeys]).
2289
2290matching_hotkey(Match, Hotkeys) ->
2291    case lists:keyfind(Match, 1, Hotkeys) of
2292	{_,Keys} -> Keys;
2293	false -> {none,axis_string(Match)}
2294    end.
2295
2296axis_string(Axis) ->
2297    case Axis of
2298	default_axis -> ?__(1,"Default Axis");
2299	element_normal -> ?__(2,"Element Normal");
2300	element_normal_edge -> ?__(3,"Element Normal (edges)");
2301	radial -> ?__(4,"Radial");
2302	from_cursor -> ?__(5,"From Cursor");
2303	from_element -> ?__(6,"From Element");
2304	from_default -> ?__(7,"From Default Point");
2305	Other -> wings_s:dir(Other)
2306    end.
2307
2308%%%
2309%%% Info Line while Tweaking
2310%%%
2311
2312info_line(Mode) ->
2313    M = mode(Mode),
2314    Cam = camera_msg(),
2315    Spc = spacebar_msg(),
2316    Extra = extra_msg(Mode),
2317    Help = wings_msg:join([M, Cam, Spc, Extra]),
2318    wings_wm:message(Help).
2319
2320camera_msg() ->
2321    ?__(1,"[C]: Tumble camera  [S]: Pan  [D]: Dolly").
2322
2323spacebar_msg() ->
2324    ?__(1,"[Space]: Switch to Tweak tool assigned to L").
2325
2326extra_msg(slide) ->
2327    "[F1]: " ++ ?__(1,"Slide Collapse");
2328extra_msg(_) -> [].
2329
2330%%%
2331%%% Right Side Info Line including Magnets and Tweak: Disabled
2332%%%
2333
2334tweak_disabled_msg() ->
2335    wings_wm:message_right(?__(1,"Tweak: Disabled")).
2336
2337tweak_magnet_help() ->
2338    {Mag, MagType, _} = wings_pref:get_value(tweak_magnet),
2339    Message = if
2340		  Mag ->
2341		      Hotkeys = wings_hotkey:matching([tweak_magnet,tweak]),
2342		      MKey = case lists:keyfind(mag_adjust, 1, Hotkeys) of
2343				 {_, Keys} -> "[" ++ wings_hotkey:format_hotkey(Keys, pretty) ++ "]";
2344				 false -> wings_s:key(alt)
2345			     end,
2346		      M1 = [?__(1,"Magnet: "),magnet_type(MagType)],
2347		      M2 = [MKey, ?__(3,"+Drag: "), ?__(4,"Adjust Radius")],
2348		      wings_msg:join([M1,M2]);
2349		  true -> ?__(2,"Magnet: Off")
2350	      end,
2351    wings_wm:message_right(Message).
2352tweak_magnet_radius_help(true) ->
2353    wings_wm:message(?__(1,"Drag right to increase and left to decrease the magnet radius."));
2354tweak_magnet_radius_help(false) ->
2355    wings_wm:message(?__(2,"Magnet is currently off.")).
2356
2357%%%
2358%%% Support for highlighting the magnet influence
2359%%%
2360
2361%% This function will clean the vertices influence information when the list is empty or
2362%% it will add the vertices influence information to Pst field of the we#
2363set_edge_influence([],_,#we{pst=Pst}) ->
2364    remove_pst(Pst);
2365set_edge_influence(Vs,VsDyn,#we{pst=Pst,es=Etab}=We) ->
2366    case wings_pref:get_value(tweak_magnet_influence) of
2367	true ->
2368	    ColFrom = col_to_vec(wings_pref:get_value(edge_color)),
2369	    ColTo = col_to_vec(wings_pref:get_value(tweak_magnet_color)),
2370	    Edges = wings_edge:from_vs(Vs,We),
2371	    EdDyn = to_edges_raw({ColFrom,ColTo},Edges,VsDyn,Etab),
2372	    add_pst(EdDyn,Pst);
2373	_ ->
2374	    Pst
2375    end.
2376
2377%% It adds the plugin functionality
2378add_pst(InfData,Pst) ->
2379    case gb_trees:lookup(?MODULE, Pst) of
2380	none ->
2381	    Data = gb_trees:empty(),
2382	    NewData = gb_trees:insert(edge_info,InfData,Data),
2383	    gb_trees:insert(?MODULE,NewData,Pst);
2384	{_,Data} ->
2385	    NewData = gb_trees:enter(edge_info,InfData,Data),
2386	    gb_trees:update(?MODULE,NewData,Pst)
2387    end.
2388
2389%% It removes the plugin functionality
2390remove_pst(none) -> none;
2391remove_pst(Pst) ->
2392    gb_trees:delete_any(?MODULE,Pst).
2393
2394%%%
2395%%% Functions of general purpose
2396%%%
2397to_edges_raw(_, [], _ , _) -> {[],<<>>};
2398to_edges_raw(_, _, [] , _) -> {[],<<>>};
2399to_edges_raw({ColFrom,ColTo}, Edges, VsDyn, Etab) ->
2400    ColRange=e3d_vec:sub(ColTo,ColFrom),
2401    to_edges_raw_1(Edges, ColFrom, ColRange, VsDyn, Etab, {[],<<>>}).
2402
2403to_edges_raw_1([], _, _, _, _, Acc) -> Acc;
2404to_edges_raw_1([Edge|Edges], Col, Range, VsDyn, Etab, {VAcc,ClBin0}) ->
2405    #edge{vs=Va0,ve=Vb0} = array:get(Edge, Etab),
2406    Infa = get_vs_influence(Va0,VsDyn),
2407    Infb = get_vs_influence(Vb0,VsDyn),
2408    {R1,G1,B1} = color_gradient(Col,Range,Infa),
2409    {R2,G2,B2} = color_gradient(Col,Range,Infb),
2410    ClBin = <<R1:?F32,G1:?F32,B1:?F32,R2:?F32,G2:?F32,B2:?F32,ClBin0/binary>>,
2411    VsPair={Va0,Vb0},
2412    to_edges_raw_1(Edges, Col, Range, VsDyn, Etab, {[VsPair|VAcc],ClBin}).
2413
2414get_vs_influence(V, VsDyn) ->
2415    case lists:keysearch(V, 1, VsDyn) of
2416        false -> 0.0;
2417        {_, {_,Value}} -> Value
2418    end.
2419
2420%%%
2421%%% Functions to produce the visual effect (inspired by wpc_magnet_mask)
2422%%%
2423
2424update_dlist({edge_info,{EdList,ClBin}},
2425	     #dlo{plugins=Pdl,src_we=#we{vp=Vtab}}=D, _) ->
2426    Key = ?MODULE,
2427    case EdList of
2428	[] ->
2429	    D#dlo{plugins=[{Key,none}|Pdl]};
2430	_ ->
2431	    Draw = edge_fun(EdList, ClBin, Vtab),
2432	    D#dlo{plugins=[{Key,Draw}|Pdl]}
2433    end.
2434
2435edge_fun(EdList, ClBin, Vtab) ->
2436    EdBin = pump_edges(EdList,Vtab),
2437    [VboEs,VboCl] = gl:genBuffers(2),
2438    gl:bindBuffer(?GL_ARRAY_BUFFER, VboEs),
2439    gl:bufferData(?GL_ARRAY_BUFFER, byte_size(EdBin), EdBin, ?GL_STATIC_DRAW),
2440    gl:bindBuffer(?GL_ARRAY_BUFFER, VboCl),
2441    gl:bufferData(?GL_ARRAY_BUFFER, byte_size(ClBin), ClBin, ?GL_STATIC_DRAW),
2442    gl:bindBuffer(?GL_ARRAY_BUFFER, 0),
2443    N = byte_size(EdBin) div 12,
2444    D = fun(RS) ->
2445		gl:depthFunc(?GL_LEQUAL),
2446		gl:bindBuffer(?GL_ARRAY_BUFFER, VboEs),
2447		gl:vertexPointer(3, ?GL_FLOAT, 0, 0),
2448		gl:bindBuffer(?GL_ARRAY_BUFFER, VboCl),
2449		gl:colorPointer(3, ?GL_FLOAT, 0, 0),
2450		gl:bindBuffer(?GL_ARRAY_BUFFER, 0),
2451		gl:enableClientState(?GL_COLOR_ARRAY),
2452		gl:enableClientState(?GL_VERTEX_ARRAY),
2453		gl:drawArrays(?GL_LINES, 0, N),
2454		gl:disableClientState(?GL_VERTEX_ARRAY),
2455		gl:disableClientState(?GL_COLOR_ARRAY),
2456		gl:depthFunc(?GL_LESS),
2457                RS
2458	end,
2459    {call,D,[{vbo,VboEs},{vbo,VboCl}]}.
2460
2461%% pumping Lines
2462pump_edges(EdList, Vtab) ->
2463    pump_edges_1(EdList, Vtab, <<>>).
2464pump_edges_1([], _,Bin) -> Bin;
2465pump_edges_1([{Id1,Id2}|SegInf], Vtab, VsBin0) ->
2466    VsBin =
2467        case {array:get(Id1, Vtab),array:get(Id2, Vtab)} of
2468            {undefined,_} -> VsBin0;
2469            {_,undefined} -> VsBin0;
2470            {{X1,Y1,Z1},{X2,Y2,Z2}} ->
2471                <<VsBin0/binary,X1:?F32,Y1:?F32,Z1:?F32,X2:?F32,Y2:?F32,Z2:?F32>>
2472        end,
2473    pump_edges_1(SegInf,Vtab,VsBin).
2474
2475%% It'll will provide the vertices data for 'update_dlist' function
2476get_data(update_dlist, Data, Acc) ->  % for draw lists
2477    case gb_trees:lookup(edge_info, Data) of
2478        none ->
2479            {ok, Acc};
2480        {_,EdgeInfo} ->
2481            {ok, [{plugin, {?MODULE, {edge_info, EdgeInfo}}}|Acc]}
2482    end.
2483
2484%% It'll use the list prepared by 'update_dlist' function and then draw it (only for plain draw)
2485draw(plain, EdgeList, _D, SelMode, RS) ->
2486    gl:lineWidth(edge_width(SelMode)),
2487    wings_dl:call(EdgeList, RS);
2488draw(_,_,_,_, RS) -> RS.
2489
2490edge_width(edge) -> float(wings_pref:get_value(edge_width));
2491edge_width(_) -> 1.0.
2492
2493col_to_vec({R,G,B}) when is_integer(R) -> {R/255.0,G/255.0,B/255.0};
2494col_to_vec({_,_,_}=Col) -> Col;
2495col_to_vec({R,G,B,_}) when is_integer(R) -> col_to_vec({R,G,B});
2496col_to_vec({R,G,B,_}) -> col_to_vec({R,G,B}).
2497
2498color_gradient(Cb, Cr, Perc) ->
2499    e3d_vec:add_prod(Cb, Cr, Perc).
2500
2501
2502%%%
2503help_msg() ->
2504    [help_msg_basic(),
2505     help_msg_magnet(),
2506     help_msg_axes(),
2507     help_msg_keys(),
2508     help_msg_using_keys(),
2509     help_msg_hotkeys(),
2510     help_msg_palette()].
2511
2512help_msg_basic() ->
2513    [{bold,?__(1,"--What is Tweak?--")},"\n",
2514     ?__(2,"Tweak lets you edit a model quickly by clicking and dragging geometry."),"\n",
2515     ?__(3,"While Tweak is enabled, all the regular Wings commands are still available,"),
2516     ?__(4," but certain mouse buttons and modifier keys will activate the Tweak tools."),cr()].
2517
2518help_msg_magnet() ->
2519    [{bold,?__(1,"--Magnets--")},"\n",
2520     {bullet,
2521      [?__(2,"Magnets allow for soft selection."),
2522       ?__(3,"There are 3 magnet types: Dome, Straight, and Spike."),
2523       ?__(4,"Adjust the Magnet's Radius by holding [Alt] and moving the mouse."),
2524       ?__(5,"Commands for switching magnets can be assigned to hotkeys in the Tweak Menu|Magnets."),
2525       ?__(6,"Magnet options can be set in the Tweak Preferences.")]}].
2526
2527help_msg_axes() ->
2528    [{bold,?__(1,"--Axis Constraints--")},"\n",
2529     {bullet,
2530      [?__(2,"Axis Constraints only affect Tweak Move and Scale operations."),
2531       ?__(3,"[F1], [F2], and [F3] can be held to constrain movement to the 3 cardinal axes, X, Y, and Z."),
2532       ?__(4,"Click axis constraint hotkeys to toggle them."),"\n",
2533       ?__(5,"Any of the axes can be toggled to stay on via the Tweak Menu or the Tweak Palette."),
2534       ?__(6,"The From Element and From Point axes activate the Tweak Vector."),
2535       ?__(7,"Axes can be combined.")]}].
2536
2537help_msg_keys() ->
2538    C = ", ",
2539    TweakTools = "("++mode(move)++C++mode(move_normal)++C++mode(scale)++C++
2540	mode(scale_uniform)++C++mode(relax)++C++?__(3," and ")++
2541	mode(slide)++")",
2542    Str = io_lib:format(?__(2,"Each of the Tweak Tools ~s can be assigned a modifier key combination (Ctrl, Shift, Alt)."),[TweakTools]),
2543    [{bold,?__(1,"--Assigning Tweak Keys--")},"\n",
2544     Str,"\n",
2545     ?__(4,"To assign a key combination to a Tweak Tool:"),"\n",
2546     ?__(5,"\t1) Open the Tweak Menu."),"\n",
2547     ?__(6,"\t2) Highlight one of the Tweak Tools."),"\n",
2548     ?__(7,"\t3) Press and hold the modifier key combination and/or press the activating mouse button."),cr()].
2549
2550help_msg_using_keys() ->
2551    [{bold,?__(1,"--Using Tweak--")},"\n",
2552     ?__(2,"1) Enabled Tweak."),"\n",
2553     ?__(3,"2) Highlight or select one or more elements of geometry."),"\n",
2554     ?__(4,"3) Press the Tweak Key combination and associated mouse button to activate Tweak."),"\n",
2555     ?__(5,"4) Drag geometry to it new position."),"\n",
2556     ?__(6,"5) Release the mouse button to complete the Tweak."),cr(),
2557     ?__(7,"Releasing the Tweak Keys will not end the Tweak event."),
2558     ?__(8,"This allows other hotkeys to be used in mid-tweak to activate another Tweak tool, an axis constraint, aim the camera, or adjust the magnet radius."),"\n",
2559     ?__(9,"Pressing [C] in mid-tweak tumbles the camera."),"\n",
2560     ?__(10,"Pressing [S] or the Arrow Keys in mid-tweak pans the camera."),"\n",
2561     ?__(11,"Pressing [D] in mid-tweak dollies the camera."),"\n",
2562     ?__(12,"Pressing [Spacebar] in mid-tweak switches to the Tweak Tool assigned to the Lmb."),"\n",
2563     ?__(13,"Holding [F] while finalizing a Slide operation collapses all newly created short edges."),cr()].
2564
2565help_msg_hotkeys() ->
2566    [{bold,?__(1,"--Hotkeyed Tweak Tools--")},"\n",
2567     ?__(2,"An alternative to usings Tweak Key combinations is to assign hotkeys to the various Tweak Tools usings the [Insert] method."),"\n",
2568     ?__(3,"Pressing the hotkey assigns that Tweak Tool to Lmb."),cr()].
2569
2570help_msg_palette() ->
2571    [{bold,?__(1,"--Tweak Palette (Window|Tweak Palette)--")},"\n",
2572     ?__(2,"The Tweak Palette is a group of 3 windows that contain the main commands from the Tweak Menu."),"\n",
2573     ?__(3,"Use the Tweak Palette to switch between Tweak Tools, Magnet Types, or Axis Constraints.")].
2574
2575cr() -> "\n\n".
2576