Detachable project tabs

This commit is contained in:
JannisX11 2022-10-28 22:56:39 +02:00
parent ed1724170e
commit 8af0c8e3d4
5 changed files with 225 additions and 30 deletions

View File

@ -675,6 +675,29 @@
opacity: 1;
}
}
#drag_out_window_helper {
width: 200px;
height: 120px;
position: absolute;
margin: -14px -40px;
background-color: var(--color-ui);
border: 3px solid var(--color-frame);
box-shadow: 1px 1px 10px rgb(0 0 0 / 40%);
background-image: url('../assets/logo_cutout.svg');
background-position: center 48px;
background-size: 40px;
background-repeat: no-repeat;
z-index: 200;
cursor: pointer;
}
#drag_out_window_helper > div {
width: 100%;
height: 26px;
text-align: center;
overflow: hidden;
white-space: nowrap;
background-color: var(--color-frame);
}
/*Status Bar*/

View File

@ -92,6 +92,19 @@ function loadOpenWithBlockbenchFile() {
ipcRenderer.on('open-model', (event, path) => {
load(path);
})
ipcRenderer.on('load-tab', (event, model) => {
let fake_file = {
path: model.editor_state?.save_path || ''
};
Codecs.project.load(model, fake_file);
if (model.detached_uuid) {
ipcRenderer.send('close-detached-project', model.detached_window_id, model.detached_uuid);
}
})
ipcRenderer.on('close-detached-project', (event, uuid) => {
let tab = ModelProject.all.find(project => project.uuid == uuid && project.detached);
if (tab) tab.close(true);
})
if (electron.process.argv.length >= 2) {
let path = electron.process.argv.last();
load(path);

View File

@ -122,6 +122,29 @@ var codec = new Codec('project', {
if (options.flag) {
model.flag = options.flag;
}
if (options.editor_state) {
Project.saveEditorState();
model.editor_state = {
save_path: Project.save_path,
export_path: Project.export_path,
saved: Project.saved,
added_models: Project.added_models,
mode: Project.mode,
tool: Project.tool,
display_uv: Project.display_uv,
exploded_view: Project.exploded_view,
uv_viewport: Project.uv_viewport,
previews: JSON.parse(JSON.stringify(Project.previews)),
selected_elements: Project.selected_elements.map(e => e.uuid),
selected_group: Project.selected_group?.uuid,
selected_vertices: JSON.parse(JSON.stringify(Project.selected_vertices)),
selected_faces: Project.selected_faces,
selected_texture: Project.selected_texture?.uuid,
};
}
model.elements = []
elements.forEach(el => {
var obj = el.getSaveCopy(model.meta)
@ -335,6 +358,39 @@ var codec = new Codec('project', {
Canvas.updateAllPositions()
Validator.validate()
this.dispatchEvent('parsed', {model})
if (model.editor_state) {
let state = model.editor_state;
Merge.string(Project, state, 'save_path')
Merge.string(Project, state, 'export_path')
Merge.boolean(Project, state, 'saved')
Merge.number(Project, state, 'added_models')
Merge.string(Project, state, 'mode')
Merge.string(Project, state, 'tool')
Merge.string(Project, state, 'display_uv')
Merge.boolean(Project, state, 'exploded_view')
if (state.uv_viewport) {
Merge.number(Project.uv_viewport, state.uv_viewport, 'zoom')
Merge.arrayVector2(Project.uv_viewport = state.uv_viewport, 'offset');
}
if (state.previews) {
for (let id in state.previews) {
Project.previews[id] = state.previews[id];
}
}
state.selected_elements.forEach(uuid => {
let el = Outliner.elements.find(el2 => el2.uuid == uuid);
Project.selected_elements.push(el);
})
Group.selected = (state.selected_group && Group.all.find(g => g.uuid == state.selected_group));
for (let key in state.selected_vertices) {
Project.selected_vertices[key] = state.selected_vertices[key];
}
Project.selected_faces.replace(state.selected_faces);
(state.selected_texture && Texture.all.find(t => t.uuid == state.selected_texture))?.select();
Project.loadEditorState();
}
},
merge(model, path) {

View File

@ -142,15 +142,25 @@ class ModelProject {
this.on_next_upen.push(callback);
}
}
select() {
if (this === Project) return true;
if (this.locked || Project.locked) return false;
if (Project) {
Project.unselect();
Blockbench.addFlag('switching_project');
} else {
Interface.tab_bar.new_tab.visible = false;
}
saveEditorState() {
this.tool = Toolbox.selected.id;
UVEditor.saveViewportOffset();
Preview.all.forEach(preview => {
this.previews[preview.id] = {
position: preview.camera.position.toArray(),
target: preview.controls.target.toArray(),
orthographic: preview.isOrtho,
zoom: preview.camOrtho.zoom,
angle: preview.angle,
}
})
Blockbench.dispatchEvent('save_editor_state', {project: this});
return this;
}
loadEditorState() {
Project = this;
Undo = this.undo;
this.selected = true;
@ -209,10 +219,25 @@ class ModelProject {
if (data.zoom) preview.camOrtho.zoom = data.zoom;
if (data.angle) preview.setLockedAngle(data.angle);
} else if (preview.default_angle !== undefined) {
setTimeout(() => preview.loadAnglePreset(preview.default_angle), 0);
preview.loadAnglePreset(preview.default_angle);
}
})
Blockbench.dispatchEvent('load_editor_state', {project: this});
return this;
}
select() {
if (this === Project) return true;
if (this.locked || Project.locked) return false;
if (Project) {
Project.unselect();
Blockbench.addFlag('switching_project');
} else {
Interface.tab_bar.new_tab.visible = false;
}
this.loadEditorState();
if (this.EditSession) {
Interface.Panels.chat.inside_vue.chat_history = this.EditSession.chat_history;
this.EditSession.catchUp();
@ -248,25 +273,15 @@ class ModelProject {
this.thumbnail = Texture.getDefault()?.source;
}
this.tool = Toolbox.selected.id;
UVEditor.saveViewportOffset();
this.saveEditorState();
}
Interface.tab_bar.last_opened_project = this.uuid;
if (Format && typeof Format.onDeactivation == 'function') {
Format.onDeactivation()
}
Preview.all.forEach(preview => {
this.previews[preview.id] = {
position: preview.camera.position.toArray(),
target: preview.controls.target.toArray(),
orthographic: preview.isOrtho,
zoom: preview.camOrtho.zoom,
angle: preview.angle,
}
})
this.undo.closeAmendEditMenu();
Preview.all.forEach(preview => {
if (preview.movingBackground) preview.stopMovingBackground();
@ -639,6 +654,8 @@ onVueSetup(() => {
let active = false;
let timeout;
let last_event = e1;
let outside_tab_bar = false;
let drag_out_window_helper;
let tab_node = e1.target;
if (!tab_node.classList.contains('project_tab') || ModelProject.all.indexOf(tab) < 0) return;
@ -674,6 +691,27 @@ onVueSetup(() => {
let index_offset = Math.trunc((e2.clientX - e1.clientX) / tab_node.clientWidth);
scope.drag_position_index = scope.drag_target_index + index_offset;
// Detach tab
let outside_tab_bar_before = outside_tab_bar;
outside_tab_bar = isApp && Math.abs(e2.clientY - 42) > 60 || e2.clientX < 2 || e2.clientX > window.innerWidth;
if (outside_tab_bar !== outside_tab_bar_before) {
//setStartScreen(outside_tab_bar);
if (!drag_out_window_helper) {
drag_out_window_helper = Interface.createElement('div', {id: 'drag_out_window_helper'}, Interface.createElement('div', {}, tab.name));
}
if (outside_tab_bar) {
document.body.append(drag_out_window_helper);
} else {
document.body.removeChild(drag_out_window_helper);
}
tab_node.style.visibility = outside_tab_bar ? 'hidden' : 'visible';
}
if (outside_tab_bar) {
drag_out_window_helper.style.left = `${e2.clientX}px`;
drag_out_window_helper.style.top = `${e2.clientY}px`;
}
}
last_event = e2;
}
@ -688,7 +726,34 @@ onVueSetup(() => {
if (Blockbench.isTouch) clearTimeout(timeout);
if (active && !open_menu) {
if (isApp && outside_tab_bar && !tab.EditSession) {
let project = Codecs.project.compile({editor_state: true, history: true, uuids: true, bitmaps: true, raw: true})
let pos = currentwindow.getPosition()
project.detached_uuid = Project.uuid;
project.detached_window_id = currentwindow.id;
ipcRenderer.send('new-window', JSON.stringify(project), JSON.stringify({
offset: [
pos[0] + e2.clientX,
pos[1] + e2.clientY,
]
}));
drag_out_window_helper.remove();
//setStartScreen(false);
tab_node.style.visibility = null;
//tab.select();
tab.detached = true;
/*Blockbench.showMessageBox({
title: 'Project detached',
message: 'The tab was moved to another window. Do you want to close it here?',
buttons: ['dialog.ok', 'dialog.cancel']
}, val => {
if (val == 0) {
tab.close(true)
}
})*/
} else if (active && !open_menu) {
convertTouchEvent(e2);
let index_offset = Math.trunc((e2.clientX - e1.clientX) / tab_node.clientWidth);
if (index_offset) {

52
main.js
View File

@ -8,6 +8,7 @@ require('@electron/remote/main').initialize()
let orig_win;
let all_wins = [];
let load_project_data;
const LaunchSettings = {
path: path.join(app.getPath('userData'), 'launch_settings.json'),
@ -35,19 +36,21 @@ if (LaunchSettings.get('hardware_acceleration') == false) {
app.disableHardwareAcceleration();
}
function createWindow(second_instance) {
function createWindow(second_instance, options = {}) {
if (app.requestSingleInstanceLock && !app.requestSingleInstanceLock()) {
app.quit()
return;
}
let win = new BrowserWindow({
icon:'icon.ico',
let win_options = {
icon: 'icon.ico',
show: false,
backgroundColor: '#21252b',
frame: LaunchSettings.get('native_window_frame') === true,
titleBarStyle: 'hidden',
minWidth: 640,
minHeight: 480,
width: 1080,
height: 720,
webPreferences: {
webgl: true,
webSecurity: true,
@ -55,7 +58,12 @@ function createWindow(second_instance) {
contextIsolation: false,
enableRemoteModule: true
}
})
};
if (options.position) {
win_options.x = options.position[0] - 300;
win_options.y = Math.max(options.position[1] - 100, 0);
}
let win = new BrowserWindow(win_options)
if (!orig_win) orig_win = win;
all_wins.push(win);
@ -133,7 +141,7 @@ function createWindow(second_instance) {
win.setMenu(null);
}
win.maximize()
if (options.maximize !== false) win.maximize()
win.show()
win.loadURL(url.format({
@ -184,8 +192,33 @@ ipcMain.on('edit-launch-setting', (event, arg) => {
ipcMain.on('add-recent-project', (event, path) => {
app.addRecentDocument(path);
})
ipcMain.on('new-window', (event, path) => {
createWindow(true);
ipcMain.on('new-window', (event, data, position) => {
if (typeof data == 'string') load_project_data = JSON.parse(data);
if (position) {
position = JSON.parse(position)
let place_in_window = all_wins.find(win => {
if (win.isDestroyed() || win.webContents == event.sender || win.isMinimized()) return false;
let pos = win.getPosition();
let size = win.getSize();
return (position.offset[0] >= pos[0] && position.offset[0] <= pos[0] + size[0]
&& position.offset[1] >= pos[1] && position.offset[1] <= pos[1] + size[1]);
})
if (place_in_window) {
place_in_window.send('load-tab', load_project_data);
load_project_data = null;
} else {
createWindow(true, {
maximize: false,
position: position.offset
});
}
} else {
createWindow(true);
}
})
ipcMain.on('close-detached-project', async (event, window_id, uuid) => {
let window = all_wins.find(win => win.id == window_id);
if (window) window.send('close-detached-project', uuid);
})
ipcMain.on('request-color-picker', async (event, arg) => {
const color = await getColorHexRGB().catch((error) => {
@ -207,6 +240,11 @@ app.on('ready', () => {
let app_was_loaded = false;
ipcMain.on('app-loaded', () => {
if (load_project_data) {
all_wins[all_wins.length-1].send('load-tab', load_project_data);
load_project_data = null;
}
if (app_was_loaded) {
console.log('[Blockbench] App reloaded')
return;