input_double.vala 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211
  1. /*
  2. * Copyright (c) 2012-2025 Daniele Bartolini et al.
  3. * SPDX-License-Identifier: GPL-3.0-or-later
  4. */
  5. namespace Crown
  6. {
  7. public class InputDouble : InputField, Gtk.Entry
  8. {
  9. public bool _inconsistent;
  10. public double _min;
  11. public double _max;
  12. public double _value;
  13. public int _preview_decimals;
  14. public int _edit_decimals;
  15. public Gtk.GestureMultiPress _gesture_click;
  16. public Gtk.EventControllerScroll _controller_scroll;
  17. public void set_inconsistent(bool inconsistent)
  18. {
  19. if (_inconsistent != inconsistent) {
  20. _inconsistent = inconsistent;
  21. if (_inconsistent) {
  22. this.text = INCONSISTENT_LABEL;
  23. } else {
  24. set_value_safe(string_to_double(this.text, _value));
  25. }
  26. }
  27. }
  28. public bool is_inconsistent()
  29. {
  30. return _inconsistent;
  31. }
  32. public GLib.Value union_value()
  33. {
  34. return this.value;
  35. }
  36. public void set_union_value(GLib.Value v)
  37. {
  38. this.value = (double)v;
  39. }
  40. public double value
  41. {
  42. get
  43. {
  44. return _value;
  45. }
  46. set
  47. {
  48. set_value_safe(value);
  49. }
  50. }
  51. public InputDouble(double val, double min, double max, int preview_decimals = 4, int edit_decimals = 5)
  52. {
  53. this.input_purpose = Gtk.InputPurpose.NUMBER;
  54. this.set_width_chars(1);
  55. this.activate.connect(on_activate);
  56. this.focus_in_event.connect(on_focus_in);
  57. this.focus_out_event.connect(on_focus_out);
  58. _inconsistent = false;
  59. _min = min;
  60. _max = max;
  61. _preview_decimals = preview_decimals;
  62. _edit_decimals = edit_decimals;
  63. set_value_safe(val);
  64. _gesture_click = new Gtk.GestureMultiPress(this);
  65. _gesture_click.pressed.connect(on_button_pressed);
  66. _gesture_click.released.connect(on_button_released);
  67. #if CROWN_GTK3
  68. this.scroll_event.connect(() => {
  69. GLib.Signal.stop_emission_by_name(this, "scroll-event");
  70. return Gdk.EVENT_PROPAGATE;
  71. });
  72. #else
  73. _controller_scroll = new Gtk.EventControllerScroll(this, Gtk.EventControllerScrollFlags.BOTH_AXES);
  74. _controller_scroll.set_propagation_phase(Gtk.PropagationPhase.CAPTURE);
  75. _controller_scroll.scroll.connect(() => {
  76. // Do nothing, just consume the event to stop
  77. // the annoying scroll default behavior.
  78. });
  79. #endif
  80. }
  81. private void on_button_pressed(int n_press, double x, double y)
  82. {
  83. this.grab_focus();
  84. }
  85. private void on_button_released(int n_press, double x, double y)
  86. {
  87. uint button = _gesture_click.get_current_button();
  88. if (button == Gdk.BUTTON_PRIMARY && this.has_focus) {
  89. if (_inconsistent)
  90. this.text = "";
  91. else
  92. this.text = print_max_decimals(_value, _edit_decimals);
  93. GLib.Idle.add(() => {
  94. this.set_position(-1);
  95. this.select_region(0, -1);
  96. return GLib.Source.REMOVE;
  97. });
  98. }
  99. }
  100. private void on_activate()
  101. {
  102. this.select_region(0, 0);
  103. this.set_position(-1);
  104. if (this.text != print_max_decimals(_value, _edit_decimals))
  105. set_value_safe(string_to_double(this.text, _value));
  106. else
  107. this.text = print_max_decimals(_value, _preview_decimals);
  108. }
  109. private bool on_focus_in(Gdk.EventFocus ev)
  110. {
  111. var app = (LevelEditorApplication)GLib.Application.get_default();
  112. app.entry_any_focus_in(this);
  113. if (_inconsistent)
  114. this.text = "";
  115. else
  116. this.text = print_max_decimals(_value, _edit_decimals);
  117. this.set_position(-1);
  118. this.select_region(0, -1);
  119. return Gdk.EVENT_PROPAGATE;
  120. }
  121. private bool on_focus_out(Gdk.EventFocus ef)
  122. {
  123. var app = (LevelEditorApplication)GLib.Application.get_default();
  124. app.entry_any_focus_out(this);
  125. if (_inconsistent) {
  126. if (this.text != "") {
  127. set_value_safe(string_to_double(this.text, _value));
  128. } else {
  129. this.text = INCONSISTENT_LABEL;
  130. }
  131. } else {
  132. if (this.text != print_max_decimals(_value, _edit_decimals))
  133. set_value_safe(string_to_double(this.text, _value));
  134. else
  135. this.text = print_max_decimals(_value, _preview_decimals);
  136. }
  137. this.select_region(0, 0);
  138. return Gdk.EVENT_PROPAGATE;
  139. }
  140. private void set_value_safe(double val)
  141. {
  142. double clamped = val.clamp(_min, _max);
  143. // Convert to text for displaying.
  144. this.text = print_max_decimals(clamped, _preview_decimals);
  145. _inconsistent = false;
  146. // Notify value changed.
  147. if (_value != clamped) {
  148. _value = clamped;
  149. value_changed(this);
  150. }
  151. }
  152. /// Returns @a str as double or @a deffault if conversion fails.
  153. private double string_to_double(string str, double deffault)
  154. {
  155. TinyExpr.Variable vars[] =
  156. {
  157. { "x", &_value }
  158. };
  159. int err;
  160. TinyExpr.Expr expr = TinyExpr.compile(str, vars, out err);
  161. return err == 0 ? TinyExpr.eval(expr) : deffault;
  162. }
  163. public void set_min(double min)
  164. {
  165. _min = min;
  166. set_value_safe(_value);
  167. }
  168. public void set_max(double max)
  169. {
  170. _max = max;
  171. set_value_safe(_value);
  172. }
  173. }
  174. } /* namespace Crown */