mirror of
https://github.com/SoftFever/OrcaSlicer.git
synced 2025-07-07 15:07:31 -06:00
Support custom IP camera (#3415)
* Support custom IP camera Allow adding custom IP camera source while keeping possible built-in bambulab camera also functional and add button to switch between them. This uses WebView widget to show the stream. Upon loading the page, javascript is used to remove native controls and scroll- bars for aesthetic reasons. * Add partial support for PiP video HTMLVideoElement supports picture-in-picture video but the dedicated control is hidden in this implementation to have more integrated look in OrcaSlicer. Add right-click listener to the camera switch icon that opens the video element in a PiP window. Only works when the video is in <video> HTML element, so for example MJPEG streams in <img> element won't work.
This commit is contained in:
parent
d26513e635
commit
b7b22eb78f
6 changed files with 346 additions and 0 deletions
76
resources/images/camera_switch.svg
Normal file
76
resources/images/camera_switch.svg
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<svg
|
||||||
|
width="39"
|
||||||
|
height="22"
|
||||||
|
viewBox="0 0 39 22"
|
||||||
|
fill="none"
|
||||||
|
version="1.1"
|
||||||
|
id="svg5"
|
||||||
|
sodipodi:docname="camera_switch.svg"
|
||||||
|
inkscape:version="1.3.2 (091e20e, 2023-11-25, custom)"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg">
|
||||||
|
<defs
|
||||||
|
id="defs5">
|
||||||
|
<clipPath
|
||||||
|
id="clip0_7112_29362">
|
||||||
|
<rect
|
||||||
|
id="svg_1"
|
||||||
|
fill="#ffffff"
|
||||||
|
height="30"
|
||||||
|
width="30"
|
||||||
|
x="0"
|
||||||
|
y="0" />
|
||||||
|
</clipPath>
|
||||||
|
</defs>
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="namedview5"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#666666"
|
||||||
|
borderopacity="1.0"
|
||||||
|
inkscape:showpageshadow="2"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
inkscape:pagecheckerboard="0"
|
||||||
|
inkscape:deskcolor="#d1d1d1"
|
||||||
|
inkscape:zoom="16"
|
||||||
|
inkscape:cx="6.84375"
|
||||||
|
inkscape:cy="11.21875"
|
||||||
|
inkscape:window-width="2560"
|
||||||
|
inkscape:window-height="1369"
|
||||||
|
inkscape:window-x="-8"
|
||||||
|
inkscape:window-y="-8"
|
||||||
|
inkscape:window-maximized="1"
|
||||||
|
inkscape:current-layer="svg5"
|
||||||
|
showguides="false" />
|
||||||
|
<path
|
||||||
|
fill-rule="evenodd"
|
||||||
|
clip-rule="evenodd"
|
||||||
|
d="m 21.066182,15.123836 h 1.463398 1.463399 1.463398 1.463399 m 0,0 h 1.463398 c 1.167463,0 2.113821,-0.946358 2.113821,-2.113821 V 2.600884 c 0,-1.1674317 -0.946358,-2.11382107 -2.113821,-2.11382107 H 16.675987 c -1.167411,0 -2.1138,0.94637877 -2.1138,2.11381047 v 1.3011427 1.3011428 1.3011427 1.3011427 h 1.268271 V 6.5043016 5.2031589 3.9020161 2.6008734 c 0,-0.4669642 0.378586,-0.8455179 0.845529,-0.8455179 h 11.707187 c 0.467048,0 0.845528,0.3785537 0.845528,0.8455285 v 10.409131 c 0,0.467049 -0.37848,0.845529 -0.845528,0.845529 h -1.463398 m 0,0 H 25.456377 23.992979 22.52958 21.066182 m 0,0 v 1.268292"
|
||||||
|
fill="#b3b3b5"
|
||||||
|
id="path1"
|
||||||
|
style="fill:#262e30;fill-opacity:1;stroke:none;stroke-width:0;stroke-dasharray:none"
|
||||||
|
sodipodi:nodetypes="ccccccssssssccccccccsssssscccccccc" />
|
||||||
|
<path
|
||||||
|
fill-rule="evenodd"
|
||||||
|
clip-rule="evenodd"
|
||||||
|
d="m 31.074174,7.804414 4.093731,3.449544 V 4.3548482 Z M 30.065035,6.9961839 c -0.501081,0.4222886 -0.501081,1.1942138 0,1.6164496 l 4.633179,3.9041215 c 0.687309,0.579187 1.737984,0.09058 1.737984,-0.80822 V 3.9003132 c 0,-0.8987967 -1.050675,-1.3873959 -1.737984,-0.80823 z"
|
||||||
|
fill="#b3b3b5"
|
||||||
|
id="path4"
|
||||||
|
style="fill:#262e30;fill-opacity:1;stroke-width:1.05691" />
|
||||||
|
<path
|
||||||
|
fill-rule="evenodd"
|
||||||
|
clip-rule="evenodd"
|
||||||
|
d="M 19.153511,8.3696736 H 7.4463245 c -0.4669431,0 -0.8455285,0.3785536 -0.8455285,0.8455179 V 19.624333 c 0,0.467049 0.3785854,0.845528 0.8455285,0.845528 H 19.153511 c 0.467048,0 0.845528,-0.378479 0.845528,-0.845528 V 9.215202 c 0,-0.4669748 -0.37848,-0.8455284 -0.845528,-0.8455284 z M 7.4463245,7.1013809 c -1.1674106,0 -2.1138,0.9463789 -2.1138,2.1138106 V 19.624333 c 0,1.167463 0.9463894,2.113821 2.1138,2.113821 H 19.153511 c 1.167463,0 2.113821,-0.946358 2.113821,-2.113821 V 9.215202 c 0,-1.1674317 -0.946358,-2.1138211 -2.113821,-2.1138211 z"
|
||||||
|
fill="#b3b3b5"
|
||||||
|
id="path1-5"
|
||||||
|
style="stroke:none;stroke-width:0;stroke-dasharray:none;fill:#262e30;fill-opacity:1" />
|
||||||
|
<path
|
||||||
|
fill-rule="evenodd"
|
||||||
|
clip-rule="evenodd"
|
||||||
|
d="m 21.844511,14.418731 4.093731,3.449545 v -6.89911 z m -1.009139,-0.80823 c -0.501081,0.422289 -0.501081,1.194214 0,1.61645 l 4.633179,3.904122 c 0.687309,0.579187 1.737984,0.09058 1.737984,-0.80822 v -7.808222 c 0,-0.8987971 -1.050675,-1.3873956 -1.737984,-0.8082304 z"
|
||||||
|
fill="#b3b3b5"
|
||||||
|
id="path4-1"
|
||||||
|
style="stroke-width:1.05691;fill:#262e30;fill-opacity:1" />
|
||||||
|
</svg>
|
After Width: | Height: | Size: 3.7 KiB |
76
resources/images/camera_switch_dark.svg
Normal file
76
resources/images/camera_switch_dark.svg
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<svg
|
||||||
|
width="39"
|
||||||
|
height="22"
|
||||||
|
viewBox="0 0 39 22"
|
||||||
|
fill="none"
|
||||||
|
version="1.1"
|
||||||
|
id="svg5"
|
||||||
|
sodipodi:docname="camera_switch_dark.svg"
|
||||||
|
inkscape:version="1.3.2 (091e20e, 2023-11-25, custom)"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg">
|
||||||
|
<defs
|
||||||
|
id="defs5">
|
||||||
|
<clipPath
|
||||||
|
id="clip0_7112_29362">
|
||||||
|
<rect
|
||||||
|
id="svg_1"
|
||||||
|
fill="#ffffff"
|
||||||
|
height="30"
|
||||||
|
width="30"
|
||||||
|
x="0"
|
||||||
|
y="0" />
|
||||||
|
</clipPath>
|
||||||
|
</defs>
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="namedview5"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#666666"
|
||||||
|
borderopacity="1.0"
|
||||||
|
inkscape:showpageshadow="2"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
inkscape:pagecheckerboard="0"
|
||||||
|
inkscape:deskcolor="#d1d1d1"
|
||||||
|
inkscape:zoom="16"
|
||||||
|
inkscape:cx="6.84375"
|
||||||
|
inkscape:cy="16.21875"
|
||||||
|
inkscape:window-width="2560"
|
||||||
|
inkscape:window-height="1369"
|
||||||
|
inkscape:window-x="-8"
|
||||||
|
inkscape:window-y="-8"
|
||||||
|
inkscape:window-maximized="1"
|
||||||
|
inkscape:current-layer="svg5"
|
||||||
|
showguides="false" />
|
||||||
|
<path
|
||||||
|
fill-rule="evenodd"
|
||||||
|
clip-rule="evenodd"
|
||||||
|
d="m 21.066182,15.123836 h 1.463398 1.463399 1.463398 1.463399 m 0,0 h 1.463398 c 1.167463,0 2.113821,-0.946358 2.113821,-2.113821 V 2.600884 c 0,-1.1674317 -0.946358,-2.11382107 -2.113821,-2.11382107 H 16.675987 c -1.167411,0 -2.1138,0.94637877 -2.1138,2.11381047 v 1.3011427 1.3011428 1.3011427 1.3011427 h 1.268271 V 6.5043016 5.2031589 3.9020161 2.6008734 c 0,-0.4669642 0.378586,-0.8455179 0.845529,-0.8455179 h 11.707187 c 0.467048,0 0.845528,0.3785537 0.845528,0.8455285 v 10.409131 c 0,0.467049 -0.37848,0.845529 -0.845528,0.845529 h -1.463398 m 0,0 H 25.456377 23.992979 22.52958 21.066182 m 0,0 v 1.268292"
|
||||||
|
fill="#b3b3b5"
|
||||||
|
id="path1"
|
||||||
|
style="fill:#b3b3b5;fill-opacity:1;stroke:none;stroke-width:0;stroke-dasharray:none"
|
||||||
|
sodipodi:nodetypes="ccccccssssssccccccccsssssscccccccc" />
|
||||||
|
<path
|
||||||
|
fill-rule="evenodd"
|
||||||
|
clip-rule="evenodd"
|
||||||
|
d="m 31.074174,7.804414 4.093731,3.449544 V 4.3548482 Z M 30.065035,6.9961839 c -0.501081,0.4222886 -0.501081,1.1942138 0,1.6164496 l 4.633179,3.9041215 c 0.687309,0.579187 1.737984,0.09058 1.737984,-0.80822 V 3.9003132 c 0,-0.8987967 -1.050675,-1.3873959 -1.737984,-0.80823 z"
|
||||||
|
fill="#b3b3b5"
|
||||||
|
id="path4"
|
||||||
|
style="fill:#b3b3b5;fill-opacity:1;stroke-width:1.05691" />
|
||||||
|
<path
|
||||||
|
fill-rule="evenodd"
|
||||||
|
clip-rule="evenodd"
|
||||||
|
d="M 19.153511,8.3696736 H 7.4463245 c -0.4669431,0 -0.8455285,0.3785536 -0.8455285,0.8455179 V 19.624333 c 0,0.467049 0.3785854,0.845528 0.8455285,0.845528 H 19.153511 c 0.467048,0 0.845528,-0.378479 0.845528,-0.845528 V 9.215202 c 0,-0.4669748 -0.37848,-0.8455284 -0.845528,-0.8455284 z M 7.4463245,7.1013809 c -1.1674106,0 -2.1138,0.9463789 -2.1138,2.1138106 V 19.624333 c 0,1.167463 0.9463894,2.113821 2.1138,2.113821 H 19.153511 c 1.167463,0 2.113821,-0.946358 2.113821,-2.113821 V 9.215202 c 0,-1.1674317 -0.946358,-2.1138211 -2.113821,-2.1138211 z"
|
||||||
|
fill="#b3b3b5"
|
||||||
|
id="path1-5"
|
||||||
|
style="stroke:none;stroke-width:0;stroke-dasharray:none;fill:#b3b3b5;fill-opacity:1" />
|
||||||
|
<path
|
||||||
|
fill-rule="evenodd"
|
||||||
|
clip-rule="evenodd"
|
||||||
|
d="m 21.844511,14.418731 4.093731,3.449545 v -6.89911 z m -1.009139,-0.80823 c -0.501081,0.422289 -0.501081,1.194214 0,1.61645 l 4.633179,3.904122 c 0.687309,0.579187 1.737984,0.09058 1.737984,-0.80822 v -7.808222 c 0,-0.8987971 -1.050675,-1.3873956 -1.737984,-0.8082304 z"
|
||||||
|
fill="#b3b3b5"
|
||||||
|
id="path4-1"
|
||||||
|
style="stroke-width:1.05691;fill:#b3b3b5;fill-opacity:1" />
|
||||||
|
</svg>
|
After Width: | Height: | Size: 3.7 KiB |
|
@ -23,6 +23,7 @@ wxEND_EVENT_TABLE()
|
||||||
|
|
||||||
wxDEFINE_EVENT(EVT_VCAMERA_SWITCH, wxMouseEvent);
|
wxDEFINE_EVENT(EVT_VCAMERA_SWITCH, wxMouseEvent);
|
||||||
wxDEFINE_EVENT(EVT_SDCARD_ABSENT_HINT, wxCommandEvent);
|
wxDEFINE_EVENT(EVT_SDCARD_ABSENT_HINT, wxCommandEvent);
|
||||||
|
wxDEFINE_EVENT(EVT_CAM_SOURCE_CHANGE, wxCommandEvent);
|
||||||
|
|
||||||
#define CAMERAPOPUP_CLICK_INTERVAL 20
|
#define CAMERAPOPUP_CLICK_INTERVAL 20
|
||||||
|
|
||||||
|
@ -78,6 +79,34 @@ CameraPopup::CameraPopup(wxWindow *parent)
|
||||||
top_sizer->Add(0, 0, wxALL, 0);
|
top_sizer->Add(0, 0, wxALL, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// custom IP camera
|
||||||
|
m_custom_camera_input_confirm = new Button(m_panel, _L("Enable"));
|
||||||
|
m_custom_camera_input_confirm->SetBackgroundColor(wxColour(38, 166, 154));
|
||||||
|
m_custom_camera_input_confirm->SetBorderColor(wxColour(38, 166, 154));
|
||||||
|
m_custom_camera_input_confirm->SetTextColor(wxColour(0xFFFFFE));
|
||||||
|
m_custom_camera_input_confirm->SetFont(Label::Body_14);
|
||||||
|
m_custom_camera_input_confirm->SetMinSize(wxSize(FromDIP(90), FromDIP(30)));
|
||||||
|
m_custom_camera_input_confirm->SetPosition(wxDefaultPosition);
|
||||||
|
m_custom_camera_input_confirm->SetCornerRadius(FromDIP(12));
|
||||||
|
m_custom_camera_input = new TextInput(m_panel, wxEmptyString, wxEmptyString, wxEmptyString, wxDefaultPosition, wxDefaultSize);
|
||||||
|
m_custom_camera_input->GetTextCtrl()->SetHint(_L("Hostname or IP"));
|
||||||
|
m_custom_camera_input->GetTextCtrl()->SetFont(Label::Body_14);
|
||||||
|
m_custom_camera_hint = new wxStaticText(m_panel, wxID_ANY, _L("Custom camera source"));
|
||||||
|
m_custom_camera_hint->Wrap(-1);
|
||||||
|
m_custom_camera_hint->SetFont(Label::Head_14);
|
||||||
|
m_custom_camera_hint->SetForegroundColour(TEXT_COL);
|
||||||
|
|
||||||
|
m_custom_camera_input_confirm->Bind(wxEVT_BUTTON, &CameraPopup::on_camera_source_changed, this);
|
||||||
|
|
||||||
|
if (!wxGetApp().app_config->get("camera", "custom_source").empty()) {
|
||||||
|
m_custom_camera_input->GetTextCtrl()->SetValue(wxGetApp().app_config->get("camera", "custom_source"));
|
||||||
|
set_custom_cam_button_state(wxGetApp().app_config->get("camera", "enable_custom_source") == "true");
|
||||||
|
}
|
||||||
|
|
||||||
|
top_sizer->Add(m_custom_camera_hint, 0, wxALIGN_CENTER_VERTICAL | wxALIGN_LEFT | wxALL, FromDIP(5));
|
||||||
|
top_sizer->Add(0, 0, wxALL, 0);
|
||||||
|
top_sizer->Add(m_custom_camera_input, 2, wxALIGN_CENTER_VERTICAL | wxALIGN_LEFT | wxEXPAND | wxALL, FromDIP(5));
|
||||||
|
top_sizer->Add(m_custom_camera_input_confirm, 1, wxALIGN_CENTER_VERTICAL | wxALIGN_RIGHT | wxALL, FromDIP(5));
|
||||||
main_sizer->Add(top_sizer, 0, wxALL, FromDIP(10));
|
main_sizer->Add(top_sizer, 0, wxALL, FromDIP(10));
|
||||||
|
|
||||||
auto url = wxString::Format(L"https://wiki.bambulab.com/%s/software/bambu-studio/virtual-camera", L"en");
|
auto url = wxString::Format(L"https://wiki.bambulab.com/%s/software/bambu-studio/virtual-camera", L"en");
|
||||||
|
@ -132,6 +161,37 @@ void CameraPopup::sdcard_absent_hint()
|
||||||
GetEventHandler()->ProcessEvent(evt);
|
GetEventHandler()->ProcessEvent(evt);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CameraPopup::on_camera_source_changed(wxCommandEvent &event)
|
||||||
|
{
|
||||||
|
if (m_obj && !m_custom_camera_input->GetTextCtrl()->IsEmpty()) {
|
||||||
|
handle_camera_source_change();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CameraPopup::handle_camera_source_change()
|
||||||
|
{
|
||||||
|
m_custom_camera_enabled = !m_custom_camera_enabled;
|
||||||
|
|
||||||
|
set_custom_cam_button_state(m_custom_camera_enabled);
|
||||||
|
|
||||||
|
wxGetApp().app_config->set("camera", "custom_source", m_custom_camera_input->GetTextCtrl()->GetValue().ToStdString());
|
||||||
|
wxGetApp().app_config->set("camera", "enable_custom_source", m_custom_camera_enabled);
|
||||||
|
|
||||||
|
wxCommandEvent evt(EVT_CAM_SOURCE_CHANGE);
|
||||||
|
evt.SetEventObject(this);
|
||||||
|
GetEventHandler()->ProcessEvent(evt);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CameraPopup::set_custom_cam_button_state(bool state)
|
||||||
|
{
|
||||||
|
m_custom_camera_enabled = state;
|
||||||
|
auto stateColour = state ? wxColour(170, 0, 0) : wxColour(38, 166, 154);
|
||||||
|
auto stateText = state ? "Disable" : "Enable";
|
||||||
|
m_custom_camera_input_confirm->SetBackgroundColor(stateColour);
|
||||||
|
m_custom_camera_input_confirm->SetBorderColor(stateColour);
|
||||||
|
m_custom_camera_input_confirm->SetLabel(_L(stateText));
|
||||||
|
}
|
||||||
|
|
||||||
void CameraPopup::on_switch_recording(wxCommandEvent& event)
|
void CameraPopup::on_switch_recording(wxCommandEvent& event)
|
||||||
{
|
{
|
||||||
if (!m_obj) return;
|
if (!m_obj) return;
|
||||||
|
|
|
@ -14,12 +14,14 @@
|
||||||
#include "Widgets/SwitchButton.hpp"
|
#include "Widgets/SwitchButton.hpp"
|
||||||
#include "Widgets/RadioBox.hpp"
|
#include "Widgets/RadioBox.hpp"
|
||||||
#include "Widgets/PopupWindow.hpp"
|
#include "Widgets/PopupWindow.hpp"
|
||||||
|
#include "Widgets/TextInput.hpp"
|
||||||
|
|
||||||
namespace Slic3r {
|
namespace Slic3r {
|
||||||
namespace GUI {
|
namespace GUI {
|
||||||
|
|
||||||
wxDECLARE_EVENT(EVT_VCAMERA_SWITCH, wxMouseEvent);
|
wxDECLARE_EVENT(EVT_VCAMERA_SWITCH, wxMouseEvent);
|
||||||
wxDECLARE_EVENT(EVT_SDCARD_ABSENT_HINT, wxCommandEvent);
|
wxDECLARE_EVENT(EVT_SDCARD_ABSENT_HINT, wxCommandEvent);
|
||||||
|
wxDECLARE_EVENT(EVT_CAM_SOURCE_CHANGE, wxCommandEvent);
|
||||||
|
|
||||||
class CameraPopup : public PopupWindow
|
class CameraPopup : public PopupWindow
|
||||||
{
|
{
|
||||||
|
@ -50,6 +52,9 @@ protected:
|
||||||
void on_switch_recording(wxCommandEvent& event);
|
void on_switch_recording(wxCommandEvent& event);
|
||||||
void on_set_resolution();
|
void on_set_resolution();
|
||||||
void sdcard_absent_hint();
|
void sdcard_absent_hint();
|
||||||
|
void on_camera_source_changed(wxCommandEvent& event);
|
||||||
|
void handle_camera_source_change();
|
||||||
|
void set_custom_cam_button_state(bool state);
|
||||||
|
|
||||||
wxWindow * create_item_radiobox(wxString title, wxWindow *parent, wxString tooltip, int padding_left);
|
wxWindow * create_item_radiobox(wxString title, wxWindow *parent, wxString tooltip, int padding_left);
|
||||||
void select_curr_radiobox(int btn_idx);
|
void select_curr_radiobox(int btn_idx);
|
||||||
|
@ -66,6 +71,10 @@ private:
|
||||||
SwitchButton* m_switch_recording;
|
SwitchButton* m_switch_recording;
|
||||||
wxStaticText* m_text_vcamera;
|
wxStaticText* m_text_vcamera;
|
||||||
SwitchButton* m_switch_vcamera;
|
SwitchButton* m_switch_vcamera;
|
||||||
|
wxStaticText* m_custom_camera_hint;
|
||||||
|
TextInput* m_custom_camera_input;
|
||||||
|
Button* m_custom_camera_input_confirm;
|
||||||
|
bool m_custom_camera_enabled{ false };
|
||||||
wxStaticText* m_text_resolution;
|
wxStaticText* m_text_resolution;
|
||||||
wxWindow* m_resolution_options[RESOLUTION_OPTIONS_NUM];
|
wxWindow* m_resolution_options[RESOLUTION_OPTIONS_NUM];
|
||||||
wxScrolledWindow *m_panel;
|
wxScrolledWindow *m_panel;
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
#include "Widgets/Button.hpp"
|
#include "Widgets/Button.hpp"
|
||||||
#include "Widgets/StepCtrl.hpp"
|
#include "Widgets/StepCtrl.hpp"
|
||||||
#include "Widgets/SideTools.hpp"
|
#include "Widgets/SideTools.hpp"
|
||||||
|
#include "Widgets/WebView.hpp"
|
||||||
|
|
||||||
#include "BitmapCache.hpp"
|
#include "BitmapCache.hpp"
|
||||||
#include "GUI_App.hpp"
|
#include "GUI_App.hpp"
|
||||||
|
@ -889,6 +890,11 @@ StatusBasePanel::StatusBasePanel(wxWindow *parent, wxWindowID id, const wxPoint
|
||||||
StatusBasePanel::~StatusBasePanel()
|
StatusBasePanel::~StatusBasePanel()
|
||||||
{
|
{
|
||||||
delete m_media_play_ctrl;
|
delete m_media_play_ctrl;
|
||||||
|
|
||||||
|
if (m_custom_camera_view) {
|
||||||
|
delete m_custom_camera_view;
|
||||||
|
m_custom_camera_view = nullptr;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void StatusBasePanel::init_bitmaps()
|
void StatusBasePanel::init_bitmaps()
|
||||||
|
@ -922,6 +928,7 @@ void StatusBasePanel::init_bitmaps()
|
||||||
m_bitmap_timelapse_off = ScalableBitmap(this, wxGetApp().dark_mode() ? "monitor_timelapse_off_dark" : "monitor_timelapse_off", 20);
|
m_bitmap_timelapse_off = ScalableBitmap(this, wxGetApp().dark_mode() ? "monitor_timelapse_off_dark" : "monitor_timelapse_off", 20);
|
||||||
m_bitmap_vcamera_on = ScalableBitmap(this, wxGetApp().dark_mode() ? "monitor_vcamera_on_dark" : "monitor_vcamera_on", 20);
|
m_bitmap_vcamera_on = ScalableBitmap(this, wxGetApp().dark_mode() ? "monitor_vcamera_on_dark" : "monitor_vcamera_on", 20);
|
||||||
m_bitmap_vcamera_off = ScalableBitmap(this, wxGetApp().dark_mode() ? "monitor_vcamera_off_dark" : "monitor_vcamera_off", 20);
|
m_bitmap_vcamera_off = ScalableBitmap(this, wxGetApp().dark_mode() ? "monitor_vcamera_off_dark" : "monitor_vcamera_off", 20);
|
||||||
|
m_bitmap_switch_camera = ScalableBitmap(this, wxGetApp().dark_mode() ? "camera_switch_dark" : "camera_switch", 20);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -989,12 +996,27 @@ wxBoxSizer *StatusBasePanel::create_monitoring_page()
|
||||||
m_setting_button->SetMinSize(wxSize(FromDIP(38), FromDIP(24)));
|
m_setting_button->SetMinSize(wxSize(FromDIP(38), FromDIP(24)));
|
||||||
m_setting_button->SetBackgroundColour(STATUS_TITLE_BG);
|
m_setting_button->SetBackgroundColour(STATUS_TITLE_BG);
|
||||||
|
|
||||||
|
m_camera_switch_button = new wxStaticBitmap(m_panel_monitoring_title, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize(FromDIP(38), FromDIP(24)), 0);
|
||||||
|
m_camera_switch_button->SetMinSize(wxSize(FromDIP(38), FromDIP(24)));
|
||||||
|
m_camera_switch_button->SetBackgroundColour(STATUS_TITLE_BG);
|
||||||
|
m_camera_switch_button->SetBitmap(m_bitmap_switch_camera.bmp());
|
||||||
|
m_camera_switch_button->Bind(wxEVT_LEFT_DOWN, &StatusBasePanel::on_camera_switch_toggled, this);
|
||||||
|
m_camera_switch_button->Bind(wxEVT_RIGHT_DOWN, [this](auto& e) {
|
||||||
|
const std::string js_request_pip = R"(
|
||||||
|
document.querySelector('video').requestPictureInPicture();
|
||||||
|
)";
|
||||||
|
m_custom_camera_view->RunScript(js_request_pip);
|
||||||
|
});
|
||||||
|
m_camera_switch_button->Hide();
|
||||||
|
|
||||||
m_bitmap_sdcard_img->SetToolTip(_L("SD Card"));
|
m_bitmap_sdcard_img->SetToolTip(_L("SD Card"));
|
||||||
m_bitmap_timelapse_img->SetToolTip(_L("Timelapse"));
|
m_bitmap_timelapse_img->SetToolTip(_L("Timelapse"));
|
||||||
m_bitmap_recording_img->SetToolTip(_L("Video"));
|
m_bitmap_recording_img->SetToolTip(_L("Video"));
|
||||||
m_bitmap_vcamera_img->SetToolTip(_L("Go Live"));
|
m_bitmap_vcamera_img->SetToolTip(_L("Go Live"));
|
||||||
m_setting_button->SetToolTip(_L("Camera Setting"));
|
m_setting_button->SetToolTip(_L("Camera Setting"));
|
||||||
|
m_camera_switch_button->SetToolTip(_L("Switch Camera View"));
|
||||||
|
|
||||||
|
bSizer_monitoring_title->Add(m_camera_switch_button, 0, wxALIGN_CENTER_VERTICAL | wxALL, FromDIP(5));
|
||||||
bSizer_monitoring_title->Add(m_bitmap_sdcard_img, 0, wxALIGN_CENTER_VERTICAL | wxALL, FromDIP(5));
|
bSizer_monitoring_title->Add(m_bitmap_sdcard_img, 0, wxALIGN_CENTER_VERTICAL | wxALL, FromDIP(5));
|
||||||
bSizer_monitoring_title->Add(m_bitmap_timelapse_img, 0, wxALIGN_CENTER_VERTICAL | wxALL, FromDIP(5));
|
bSizer_monitoring_title->Add(m_bitmap_timelapse_img, 0, wxALIGN_CENTER_VERTICAL | wxALL, FromDIP(5));
|
||||||
bSizer_monitoring_title->Add(m_bitmap_recording_img, 0, wxALIGN_CENTER_VERTICAL | wxALL, FromDIP(5));
|
bSizer_monitoring_title->Add(m_bitmap_recording_img, 0, wxALIGN_CENTER_VERTICAL | wxALL, FromDIP(5));
|
||||||
|
@ -1014,17 +1036,44 @@ wxBoxSizer *StatusBasePanel::create_monitoring_page()
|
||||||
m_media_ctrl = new wxMediaCtrl2(this);
|
m_media_ctrl = new wxMediaCtrl2(this);
|
||||||
m_media_ctrl->SetMinSize(wxSize(PAGE_MIN_WIDTH, FromDIP(288)));
|
m_media_ctrl->SetMinSize(wxSize(PAGE_MIN_WIDTH, FromDIP(288)));
|
||||||
|
|
||||||
|
m_custom_camera_view = WebView::CreateWebView(this, wxEmptyString);
|
||||||
|
m_custom_camera_view->EnableContextMenu(false);
|
||||||
|
Bind(wxEVT_WEBVIEW_NAVIGATING, &StatusBasePanel::on_webview_navigating, this, m_custom_camera_view->GetId());
|
||||||
|
|
||||||
m_media_play_ctrl = new MediaPlayCtrl(this, m_media_ctrl, wxDefaultPosition, wxSize(-1, FromDIP(40)));
|
m_media_play_ctrl = new MediaPlayCtrl(this, m_media_ctrl, wxDefaultPosition, wxSize(-1, FromDIP(40)));
|
||||||
|
m_custom_camera_view->Hide();
|
||||||
|
m_custom_camera_view->Bind(wxEVT_WEBVIEW_SCRIPT_MESSAGE_RECEIVED, [this](wxWebViewEvent& evt) {
|
||||||
|
if (evt.GetString() == "leavepictureinpicture") {
|
||||||
|
// When leaving PiP, video gets paused in some cases and toggling play
|
||||||
|
// programmatically does not work.
|
||||||
|
m_custom_camera_view->Reload();
|
||||||
|
}
|
||||||
|
else if (evt.GetString() == "enterpictureinpicture") {
|
||||||
|
toggle_builtin_camera();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
sizer->Add(m_media_ctrl, 1, wxEXPAND | wxALL, 0);
|
sizer->Add(m_media_ctrl, 1, wxEXPAND | wxALL, 0);
|
||||||
|
sizer->Add(m_custom_camera_view, 1, wxEXPAND | wxALL, 0);
|
||||||
sizer->Add(m_media_play_ctrl, 0, wxEXPAND | wxALL, 0);
|
sizer->Add(m_media_play_ctrl, 0, wxEXPAND | wxALL, 0);
|
||||||
// media_ctrl_panel->SetSizer(bSizer_monitoring);
|
// media_ctrl_panel->SetSizer(bSizer_monitoring);
|
||||||
// media_ctrl_panel->Layout();
|
// media_ctrl_panel->Layout();
|
||||||
//
|
//
|
||||||
// sizer->Add(media_ctrl_panel, 1, wxEXPAND | wxALL, 1);
|
// sizer->Add(media_ctrl_panel, 1, wxEXPAND | wxALL, 1);
|
||||||
|
|
||||||
|
if (wxGetApp().app_config->get("camera", "enable_custom_source") == "true") {
|
||||||
|
handle_camera_source_change();
|
||||||
|
}
|
||||||
|
|
||||||
return sizer;
|
return sizer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void StatusBasePanel::on_webview_navigating(wxWebViewEvent& evt) {
|
||||||
|
wxGetApp().CallAfter([this] {
|
||||||
|
remove_controls();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
wxBoxSizer *StatusBasePanel::create_machine_control_page(wxWindow *parent)
|
wxBoxSizer *StatusBasePanel::create_machine_control_page(wxWindow *parent)
|
||||||
{
|
{
|
||||||
wxBoxSizer *bSizer_right = new wxBoxSizer(wxVERTICAL);
|
wxBoxSizer *bSizer_right = new wxBoxSizer(wxVERTICAL);
|
||||||
|
@ -3863,6 +3912,7 @@ void StatusPanel::on_camera_enter(wxMouseEvent& event)
|
||||||
}
|
}
|
||||||
sdcard_hint_dlg->on_show();
|
sdcard_hint_dlg->on_show();
|
||||||
});
|
});
|
||||||
|
m_camera_popup->Bind(EVT_CAM_SOURCE_CHANGE, &StatusPanel::on_camera_source_change, this);
|
||||||
wxWindow* ctrl = (wxWindow*)event.GetEventObject();
|
wxWindow* ctrl = (wxWindow*)event.GetEventObject();
|
||||||
wxPoint pos = ctrl->ClientToScreen(wxPoint(0, 0));
|
wxPoint pos = ctrl->ClientToScreen(wxPoint(0, 0));
|
||||||
wxSize sz = ctrl->GetSize();
|
wxSize sz = ctrl->GetSize();
|
||||||
|
@ -3874,6 +3924,71 @@ void StatusPanel::on_camera_enter(wxMouseEvent& event)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void StatusBasePanel::on_camera_source_change(wxCommandEvent& event)
|
||||||
|
{
|
||||||
|
handle_camera_source_change();
|
||||||
|
}
|
||||||
|
|
||||||
|
void StatusBasePanel::handle_camera_source_change()
|
||||||
|
{
|
||||||
|
const auto new_cam_url = wxGetApp().app_config->get("camera", "custom_source");
|
||||||
|
const auto enabled = wxGetApp().app_config->get("camera", "enable_custom_source") == "true";
|
||||||
|
|
||||||
|
if (enabled && !new_cam_url.empty()) {
|
||||||
|
m_custom_camera_view->LoadURL(new_cam_url);
|
||||||
|
toggle_custom_camera();
|
||||||
|
m_camera_switch_button->Show();
|
||||||
|
} else {
|
||||||
|
toggle_builtin_camera();
|
||||||
|
m_camera_switch_button->Hide();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void StatusBasePanel::toggle_builtin_camera()
|
||||||
|
{
|
||||||
|
m_custom_camera_view->Hide();
|
||||||
|
m_media_ctrl->Show();
|
||||||
|
m_media_play_ctrl->Show();
|
||||||
|
}
|
||||||
|
|
||||||
|
void StatusBasePanel::toggle_custom_camera()
|
||||||
|
{
|
||||||
|
const auto enabled = wxGetApp().app_config->get("camera", "enable_custom_source") == "true";
|
||||||
|
|
||||||
|
if (enabled) {
|
||||||
|
m_custom_camera_view->Show();
|
||||||
|
m_media_ctrl->Hide();
|
||||||
|
m_media_play_ctrl->Hide();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void StatusBasePanel::on_camera_switch_toggled(wxMouseEvent& event)
|
||||||
|
{
|
||||||
|
const auto enabled = wxGetApp().app_config->get("camera", "enable_custom_source") == "true";
|
||||||
|
if (enabled && m_media_ctrl->IsShown()) {
|
||||||
|
toggle_custom_camera();
|
||||||
|
} else {
|
||||||
|
toggle_builtin_camera();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void StatusBasePanel::remove_controls()
|
||||||
|
{
|
||||||
|
const std::string js_cleanup_video_element = R"(
|
||||||
|
document.body.style.overflow='hidden';
|
||||||
|
const video = document.querySelector('video');
|
||||||
|
video.setAttribute('style', 'width: 100% !important;');
|
||||||
|
video.removeAttribute('controls');
|
||||||
|
video.addEventListener('leavepictureinpicture', () => {
|
||||||
|
window.wx.postMessage('leavepictureinpicture');
|
||||||
|
});
|
||||||
|
video.addEventListener('enterpictureinpicture', () => {
|
||||||
|
window.wx.postMessage('enterpictureinpicture');
|
||||||
|
});
|
||||||
|
)";
|
||||||
|
m_custom_camera_view->RunScript(js_cleanup_video_element);
|
||||||
|
}
|
||||||
|
|
||||||
void StatusPanel::on_camera_leave(wxMouseEvent& event)
|
void StatusPanel::on_camera_leave(wxMouseEvent& event)
|
||||||
{
|
{
|
||||||
if (obj && m_camera_popup) {
|
if (obj && m_camera_popup) {
|
||||||
|
|
|
@ -287,6 +287,7 @@ protected:
|
||||||
ScalableBitmap m_bitmap_timelapse_off;
|
ScalableBitmap m_bitmap_timelapse_off;
|
||||||
ScalableBitmap m_bitmap_vcamera_on;
|
ScalableBitmap m_bitmap_vcamera_on;
|
||||||
ScalableBitmap m_bitmap_vcamera_off;
|
ScalableBitmap m_bitmap_vcamera_off;
|
||||||
|
ScalableBitmap m_bitmap_switch_camera;
|
||||||
|
|
||||||
/* title panel */
|
/* title panel */
|
||||||
wxPanel * media_ctrl_panel;
|
wxPanel * media_ctrl_panel;
|
||||||
|
@ -307,6 +308,7 @@ protected:
|
||||||
wxStaticBitmap *m_bitmap_sdcard_img;
|
wxStaticBitmap *m_bitmap_sdcard_img;
|
||||||
wxStaticBitmap *m_bitmap_static_use_time;
|
wxStaticBitmap *m_bitmap_static_use_time;
|
||||||
wxStaticBitmap *m_bitmap_static_use_weight;
|
wxStaticBitmap *m_bitmap_static_use_weight;
|
||||||
|
wxStaticBitmap* m_camera_switch_button;
|
||||||
|
|
||||||
|
|
||||||
wxMediaCtrl2 * m_media_ctrl;
|
wxMediaCtrl2 * m_media_ctrl;
|
||||||
|
@ -326,6 +328,7 @@ protected:
|
||||||
ScalableButton *m_button_pause_resume;
|
ScalableButton *m_button_pause_resume;
|
||||||
ScalableButton *m_button_abort;
|
ScalableButton *m_button_abort;
|
||||||
Button * m_button_clean;
|
Button * m_button_clean;
|
||||||
|
wxWebView * m_custom_camera_view{nullptr};
|
||||||
|
|
||||||
wxStaticText * m_text_tasklist_caption;
|
wxStaticText * m_text_tasklist_caption;
|
||||||
|
|
||||||
|
@ -410,6 +413,13 @@ protected:
|
||||||
virtual void on_axis_ctrl_z_down_10(wxCommandEvent &event) { event.Skip(); }
|
virtual void on_axis_ctrl_z_down_10(wxCommandEvent &event) { event.Skip(); }
|
||||||
virtual void on_axis_ctrl_e_up_10(wxCommandEvent &event) { event.Skip(); }
|
virtual void on_axis_ctrl_e_up_10(wxCommandEvent &event) { event.Skip(); }
|
||||||
virtual void on_axis_ctrl_e_down_10(wxCommandEvent &event) { event.Skip(); }
|
virtual void on_axis_ctrl_e_down_10(wxCommandEvent &event) { event.Skip(); }
|
||||||
|
void on_camera_source_change(wxCommandEvent& event);
|
||||||
|
void handle_camera_source_change();
|
||||||
|
void remove_controls();
|
||||||
|
void on_webview_navigating(wxWebViewEvent& evt);
|
||||||
|
void on_camera_switch_toggled(wxMouseEvent& event);
|
||||||
|
void toggle_custom_camera();
|
||||||
|
void toggle_builtin_camera();
|
||||||
|
|
||||||
public:
|
public:
|
||||||
StatusBasePanel(wxWindow * parent,
|
StatusBasePanel(wxWindow * parent,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue