You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

401 lines
12KB

  1. #include <math.h>
  2. #include "alikins.hpp"
  3. #include "ParamFloatField.hpp"
  4. #include "ui.hpp"
  5. #include "window.hpp"
  6. #include "dsp/digital.hpp"
  7. namespace rack_plugin_Alikins {
  8. /* Notes:
  9. Most of this is implemented in HoveredValueWidget.step(). Each
  10. time the gui thread steps the scene graph, .step() is checking
  11. if gHoveredWidget is a ParamWidget or Port. It is more or less
  12. polling.
  13. Is there any way to get an event when any ParamWidget or Port is
  14. hovered? (onMouseMove or onMouseEnter? but for other widgets). May
  15. be better implemented as responding to hypothetical onHoverEnter
  16. or onHoverLeave instead of polling.
  17. */
  18. /* This module was inspired by the question and discussion at
  19. https://www.facebook.com/groups/vcvrack/permalink/286752278651590/
  20. "Hi Folks, Just wondering, is it possible to see control parameter values in VCVRack?"
  21. */
  22. struct HoveredValue : Module {
  23. enum ParamIds {
  24. HOVERED_PARAM_VALUE_PARAM,
  25. HOVER_ENABLED_PARAM,
  26. OUTPUT_RANGE_PARAM,
  27. HOVERED_SCALED_PARAM_VALUE_PARAM,
  28. NUM_PARAMS
  29. };
  30. enum InputIds {
  31. NUM_INPUTS
  32. };
  33. enum OutputIds {
  34. PARAM_VALUE_OUTPUT,
  35. SCALED_PARAM_VALUE_OUTPUT,
  36. NUM_OUTPUTS
  37. };
  38. enum LightIds {
  39. NUM_LIGHTS
  40. };
  41. enum HoverEnabled {OFF, WITH_SHIFT, ALWAYS};
  42. HoveredValue() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS) {}
  43. void step() override;
  44. json_t* toJson() override;
  45. void fromJson(json_t *rootJ) override;
  46. HoverEnabled enabled = WITH_SHIFT;
  47. VoltageRange outputRange = MINUS_PLUS_FIVE;
  48. bool useTooltip = true;
  49. };
  50. void HoveredValue::step() {
  51. outputs[PARAM_VALUE_OUTPUT].value = params[HOVERED_PARAM_VALUE_PARAM].value;
  52. outputs[SCALED_PARAM_VALUE_OUTPUT].value = params[HOVERED_SCALED_PARAM_VALUE_PARAM].value;
  53. }
  54. json_t* HoveredValue::toJson() {
  55. json_t *rootJ = json_object();
  56. json_object_set_new(rootJ, "useTooltip", json_boolean(useTooltip));
  57. return rootJ;
  58. }
  59. void HoveredValue::fromJson(json_t *rootJ) {
  60. json_t *useTooltipJ = json_object_get(rootJ, "useTooltip");
  61. if (useTooltipJ) {
  62. useTooltip = json_boolean_value(useTooltipJ);
  63. }
  64. }
  65. struct HoveredValueWidget : ModuleWidget {
  66. HoveredValueWidget(HoveredValue *module);
  67. void step() override;
  68. void onChange(EventChange &e) override;
  69. void tooltipHide();
  70. void tooltipShow(std::string tooltipText, Widget *hoveredWidget);
  71. Menu *createContextMenu() override;
  72. ParamWidget *enableHoverSwitch;
  73. ParamWidget *outputRangeSwitch;
  74. ParamFloatField *param_value_field;
  75. TextField *min_field;
  76. TextField *max_field;
  77. TextField *default_field;
  78. TextField *widget_type_field;
  79. Tooltip *tooltip = NULL;
  80. };
  81. HoveredValueWidget::HoveredValueWidget(HoveredValue *module) : ModuleWidget(module) {
  82. setPanel(SVG::load(assetPlugin(plugin, "res/HoveredValue.svg")));
  83. float y_baseline = 45.0f;
  84. Vec text_field_size = Vec(70.0f, 22.0f);
  85. float x_pos = 10.0f;
  86. y_baseline = 38.0f;
  87. tooltip = new Tooltip();
  88. param_value_field = new ParamFloatField(module);
  89. param_value_field->box.pos = Vec(x_pos, y_baseline);
  90. param_value_field->box.size = text_field_size;
  91. addChild(param_value_field);
  92. y_baseline = 78.0f;
  93. min_field = new TextField();
  94. min_field->box.pos = Vec(x_pos, y_baseline);
  95. min_field->box.size = text_field_size;
  96. addChild(min_field);
  97. y_baseline = 118.0f;
  98. max_field = new TextField();
  99. max_field->box.pos = Vec(x_pos, y_baseline);
  100. max_field->box.size = text_field_size;
  101. addChild(max_field);
  102. y_baseline = 158.0f;
  103. default_field = new TextField();
  104. default_field->box.pos = Vec(x_pos, y_baseline);
  105. default_field->box.size = text_field_size;
  106. addChild(default_field);
  107. y_baseline = 198.0f;
  108. widget_type_field = new TextField();
  109. widget_type_field->box.pos = Vec(x_pos, y_baseline);
  110. widget_type_field->box.size = text_field_size;
  111. addChild(widget_type_field);
  112. // Scaled output and scaled output range
  113. // y_baseline = y_baseline + 25.0f;
  114. y_baseline = box.size.y - 128.0f;
  115. outputRangeSwitch = ParamWidget::create<CKSSThree>(Vec(5, y_baseline), module,
  116. HoveredValue::OUTPUT_RANGE_PARAM, 0.0f, 2.0f, 0.0f);
  117. addParam(outputRangeSwitch);
  118. // Scaled output port
  119. Port *scaled_value_out_port = Port::create<PJ301MPort>(
  120. Vec(60.0f, y_baseline - 2.0f),
  121. Port::OUTPUT,
  122. module,
  123. HoveredValue::SCALED_PARAM_VALUE_OUTPUT);
  124. outputs.push_back(scaled_value_out_port);
  125. addChild(scaled_value_out_port);
  126. // enabled/disable switch
  127. y_baseline = box.size.y - 65.0f;
  128. enableHoverSwitch = ParamWidget::create<CKSSThree>(Vec(5, box.size.y - 62.0f), module,
  129. HoveredValue::HOVER_ENABLED_PARAM, 0.0f, 2.0f, 0.0f);
  130. addParam(enableHoverSwitch);
  131. Port *raw_value_out_port = Port::create<PJ301MPort>(
  132. Vec(60.0f, box.size.y - 67.0f),
  133. Port::OUTPUT,
  134. module,
  135. HoveredValue::PARAM_VALUE_OUTPUT);
  136. outputs.push_back(raw_value_out_port);
  137. addChild(raw_value_out_port);
  138. addChild(Widget::create<ScrewSilver>(Vec(0.0f, 0.0f)));
  139. addChild(Widget::create<ScrewSilver>(Vec(box.size.x - 15.0f, 0.0f)));
  140. addChild(Widget::create<ScrewSilver>(Vec(0.0f, 365.0f)));
  141. addChild(Widget::create<ScrewSilver>(Vec(box.size.x - 15.0f, 365.0f)));
  142. // fire off an event to refresh all the widgets
  143. EventChange e;
  144. onChange(e);
  145. }
  146. void HoveredValueWidget::tooltipHide() {
  147. // assert?
  148. if (!tooltip) {
  149. // debug("tooltip was null");
  150. return;
  151. }
  152. if (!tooltip->parent) { return; }
  153. // debug("tooltipHide[res=%s]: tt->text:%s", reason.c_str(), tooltip->text.c_str());
  154. if (!RACK_PLUGIN_UI_RACKSCENE) {
  155. // debug("gScene was null");
  156. return;
  157. }
  158. if (tooltip->parent) {
  159. RACK_PLUGIN_UI_RACKSCENE->removeChild(tooltip);
  160. }
  161. }
  162. void HoveredValueWidget::tooltipShow(std::string tooltipText, Widget *hoveredWidget) {
  163. ModuleWidget *hoveredParent = hoveredWidget->getAncestorOfType<ModuleWidget>();
  164. if (!hoveredParent) {
  165. return;
  166. }
  167. if (!reinterpret_cast<HoveredValue *>(module)->useTooltip) {
  168. return;
  169. }
  170. // debug("hoveredParent pos (%f, %f)", hoveredParent->box.pos.x, hoveredParent->box.pos.y);
  171. Vec offsetFromHovered = Vec(20.0, -20.0f);
  172. Vec absHovered = hoveredParent->getAbsoluteOffset(hoveredWidget->box.pos);
  173. Vec hoveredOffset = absHovered.plus(offsetFromHovered);
  174. tooltip->box.pos = hoveredOffset;
  175. // debug("tooltip show: (%f, %f) pos: (%f, %f)", tooltip->box.pos.x, tooltip->box.pos.y,
  176. // hoveredWidget->box.pos.x, hoveredWidget->box.pos.y);
  177. tooltip->text = tooltipText;
  178. if (!tooltip->parent) {
  179. RACK_PLUGIN_UI_RACKSCENE->addChild(tooltip);
  180. }
  181. }
  182. void HoveredValueWidget::step() {
  183. /* TODO: would be useful to be more explicit about the state here,
  184. ie, states indicating if hovered widget is not injectable or not,
  185. and if we are actively injecting or not. And transitions for when
  186. we start hovering and stop hovering over a widget.
  187. */
  188. float display_min = -5.0f;
  189. float display_max = 5.0f;
  190. float display_default = 0.0f;
  191. std::string display_type = "";
  192. std::string tooltipText;
  193. float raw_value = 0.0f;
  194. ModuleWidget::step();
  195. bool shift_pressed = windowIsShiftPressed();
  196. if (!RACK_PLUGIN_UI_HOVERED_WIDGET) {
  197. tooltipHide();
  198. return;
  199. }
  200. if (module->params[HoveredValue::HOVER_ENABLED_PARAM].value == HoveredValue::OFF) {
  201. tooltipHide();
  202. return;
  203. }
  204. if (module->params[HoveredValue::HOVER_ENABLED_PARAM].value == HoveredValue::WITH_SHIFT &&!shift_pressed) {
  205. tooltipHide();
  206. return;
  207. }
  208. VoltageRange outputRange = (VoltageRange) int32_t(round(module->params[HoveredValue::OUTPUT_RANGE_PARAM].value));
  209. ParamWidget *pwidget = dynamic_cast<ParamWidget *>(RACK_PLUGIN_UI_HOVERED_WIDGET);
  210. if (pwidget) {
  211. // TODO: show value of original and scaled?
  212. raw_value = pwidget->value;
  213. display_min = pwidget->minValue;
  214. display_max = pwidget->maxValue;
  215. display_default = pwidget->defaultValue;
  216. display_type = "param";
  217. // TODO: if we use type name detection stuff (cxxabi/typeinfo/etc) we could possibly
  218. // also show the name of the hovered widget as a hint on mystery meat params
  219. // TODO: anyway to get the orig literal name of an enum value (ie, LFO_VC_OUTPUT etc)
  220. // at runtime? might also be hint
  221. }
  222. Port *port = dynamic_cast<Port *>(RACK_PLUGIN_UI_HOVERED_WIDGET);
  223. if (port) {
  224. if (port->type == port->INPUT) {
  225. raw_value = port->module->inputs[port->portId].value;
  226. display_type = "input";
  227. }
  228. if (port->type == port->OUTPUT) {
  229. raw_value = port->module->outputs[port->portId].value;
  230. display_type = "output";
  231. }
  232. // inputs/outputs dont have variable min/max, so just use the -10/+10 and
  233. // 0 for the default to get the point across.
  234. display_min = -10.0f;
  235. display_max = 10.0f;
  236. display_default = 0.0f;
  237. }
  238. if (!pwidget && !port) {
  239. tooltipHide();
  240. } else {
  241. // TODO build fancier tool tip text
  242. // TODO maybe just draw a widget like a tooltip, would be cool to draw a pop up a mini 'scope'
  243. tooltipShow(stringf("%s\n%#.4g", display_type.c_str(), raw_value), RACK_PLUGIN_UI_HOVERED_WIDGET);
  244. }
  245. float scaled_value = rescale(raw_value, display_min, display_max,
  246. voltage_min[outputRange],
  247. voltage_max[outputRange]);
  248. engineSetParam(module, HoveredValue::HOVERED_PARAM_VALUE_PARAM, raw_value);
  249. engineSetParam(module, HoveredValue::HOVERED_SCALED_PARAM_VALUE_PARAM, scaled_value);
  250. param_value_field->setValue(raw_value);
  251. min_field->setText(stringf("%#.4g", display_min));
  252. max_field->setText(stringf("%#.4g", display_max));
  253. default_field->setText(stringf("%#.4g", display_default));
  254. widget_type_field->setText(display_type);
  255. // TODO: if a WireWidget, can we figure out it's in/out and current value? That would be cool,
  256. // though it doesn't look like WireWidgets are ever hovered (or gHoveredWidget never
  257. // seems to be a WireWidget).
  258. }
  259. void HoveredValueWidget::onChange(EventChange &e) {
  260. ModuleWidget::onChange(e);
  261. param_value_field->onChange(e);
  262. }
  263. struct UseTooltipMenuItem : MenuItem {
  264. HoveredValue *module;
  265. void onAction(EventAction &e) override {
  266. if (module->useTooltip) {
  267. module->useTooltip = false;
  268. } else {
  269. module->useTooltip = true;
  270. }
  271. }
  272. void step() override {
  273. rightText = CHECKMARK(module->useTooltip);
  274. }
  275. };
  276. Menu *HoveredValueWidget::createContextMenu() {
  277. Menu *menu = ModuleWidget::createContextMenu();
  278. MenuLabel *spacerLabel = new MenuLabel();
  279. menu->addChild(spacerLabel);
  280. HoveredValue *hoveredValue = dynamic_cast<HoveredValue*>(module);
  281. assert(hoveredValue);
  282. UseTooltipMenuItem *useTooltipMenuItem =
  283. MenuItem::create<UseTooltipMenuItem>("Show Tooltip",
  284. CHECKMARK(hoveredValue->useTooltip > 2.0f));
  285. useTooltipMenuItem->module = hoveredValue;
  286. menu->addChild(useTooltipMenuItem);
  287. return menu;
  288. }
  289. } // namespace rack_plugin_Alikins
  290. using namespace rack_plugin_Alikins;
  291. RACK_PLUGIN_MODEL_INIT(Alikins, HoveredValue) {
  292. Model *modelHoveredValue = Model::create<HoveredValue, HoveredValueWidget>(
  293. "Alikins", "HoveredValue", "Hovered Value - get value under cursor", UTILITY_TAG, CONTROLLER_TAG);
  294. return modelHoveredValue;
  295. }