mirror of
https://github.com/JannisX11/blockbench.git
synced 2025-02-17 16:20:13 +08:00
Detachable project tabs
This commit is contained in:
parent
ed1724170e
commit
8af0c8e3d4
@ -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*/
|
||||
|
@ -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);
|
||||
|
@ -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) {
|
||||
|
||||
|
111
js/io/project.js
111
js/io/project.js
@ -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
52
main.js
@ -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;
|
||||
|
Loading…
Reference in New Issue
Block a user