material_editor.lua 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972
  1. local capi = require 'material_editor_capi'
  2. local lgi = require 'lgi'
  3. local Gtk = lgi.require('Gtk')
  4. local Gdk = lgi.require('Gdk')
  5. local GdkX11 = lgi.require('GdkX11')
  6. local GObject = lgi.require('GObject')
  7. local GLib = lgi.require('GLib')
  8. local GdkPixbuf = lgi.require('GdkPixbuf')
  9. local pprint = require 'pprint'
  10. editor_env = os.getenv("EDITOR")
  11. if editor_env then
  12. editor = GLib.find_program_in_path(editor_env)
  13. end
  14. if not editor then
  15. editor = GLib.find_program_in_path("gvim")
  16. end
  17. if not editor then
  18. editor = GLib.find_program_in_path("gedit")
  19. end
  20. if not editor then
  21. editor = GLib.find_program_in_path("emacs")
  22. end
  23. b2l_data = false
  24. current_scene = false
  25. current_object = false
  26. controls = {}
  27. settings_expanders = {}
  28. settings_grids = {}
  29. materials = {}
  30. frame_start = 0
  31. frame_end = 0
  32. frame_delta = 1
  33. playing_animation = false
  34. shutdown = false
  35. local window_main = Gtk.Window {
  36. title = 'B2L Material Editor',
  37. on_destroy = function()
  38. shutdown = true
  39. end,
  40. width_request = 250,
  41. on_realize = function (window)
  42. capi.create_glwin(window:get_window():get_xid())
  43. end,
  44. }
  45. vbox_settings = Gtk.VBox {
  46. hexpand = true
  47. }
  48. function queue_render()
  49. capi:need_redraw()
  50. end
  51. function setting_changed()
  52. save_toolbutton.sensitive = true
  53. end
  54. function update_controls(uniforms)
  55. if not b2l_data then
  56. return
  57. end
  58. if not uniforms then
  59. local dialog = Gtk.MessageDialog {
  60. parent = window,
  61. message_type = 'ERROR', buttons = 'CLOSE',
  62. text = "Failed to parse shaders",
  63. on_response = Gtk.Widget.destroy
  64. }
  65. dialog:show_all()
  66. return
  67. end
  68. --Remove references to controls this shader doesn't need
  69. for k, v in pairs(controls) do
  70. if not uniforms[k] then
  71. controls[k].widget = nil
  72. end
  73. end
  74. -- Remove all expanders
  75. for k, v in pairs(settings_expanders) do
  76. vbox_settings:remove(v)
  77. end
  78. settings_grids = {}
  79. settings_expanders = {}
  80. local grids_pos = {}
  81. for i, k in ipairs(uniforms) do
  82. local datatype = uniforms[k].datatype
  83. local tag = uniforms[k].tag
  84. if not tag then
  85. tag = "Misc"
  86. end
  87. local id = k
  88. if not active_material.params[k] then
  89. active_material.params[k] = {}
  90. end
  91. if active_material.params[k].datatype ~= datatype then
  92. active_material.params[k].datatype = datatype
  93. active_material.params[k].value = nil
  94. end
  95. active_material.params[k].tag = tag
  96. if not controls[k] then
  97. controls[k] = {datatype = v}
  98. end
  99. controls[k].tag = tag
  100. local value = active_material.params[k].value
  101. if datatype == "float" then
  102. if not value then
  103. value = 0.5
  104. end
  105. active_material.params[k].value = value
  106. local widget = Gtk.Scrollbar {
  107. orientation = "HORIZONTAL",
  108. adjustment = Gtk.Adjustment {
  109. lower = 0,
  110. upper = 1,
  111. },
  112. hexpand = true,
  113. on_value_changed = function(self)
  114. if active_material.params[k].value ~= self:get_value() then
  115. setting_changed()
  116. end
  117. active_material.params[k].value = self:get_value()
  118. queue_render()
  119. end
  120. }
  121. controls[k].widget = widget
  122. controls[k].widget:set_value(value)
  123. elseif datatype == "vec3" then
  124. if not value then
  125. value = {1, 1, 1}
  126. end
  127. active_material.params[k].value = value
  128. local widget = Gtk.ColorButton {
  129. use_alpha = false,
  130. on_color_set = function(self)
  131. local value = {self.rgba.red, self.rgba.green, self.rgba.blue }
  132. if (not active_material.params[k].value) or
  133. value[1] ~= active_material.params[k].value[1] or
  134. value[2] ~= active_material.params[k].value[2] or
  135. value[3] ~= active_material.params[k].value[3] then
  136. setting_changed()
  137. end
  138. active_material.params[k].value = {self.rgba.red, self.rgba.green, self.rgba.blue }
  139. queue_render()
  140. end
  141. }
  142. controls[k].widget = widget
  143. controls[k].widget:set_rgba(Gdk.RGBA {red = value[1], green = value[2], blue = value[3], alpha = 1})
  144. elseif datatype == "bool" then
  145. if value == nil then
  146. value = false
  147. end
  148. active_material.params[k].value = value
  149. controls[k] = {}
  150. local widget = Gtk.CheckButton {
  151. label = "",
  152. on_toggled = function(self)
  153. if active_material.params[k].value ~= self.active then
  154. setting_changed()
  155. end
  156. active_material.params[k].value = self.active
  157. queue_render()
  158. end
  159. }
  160. controls[k].widget = widget
  161. controls[k].widget:set_active(value)
  162. elseif datatype == "sampler2D" then
  163. if value ~= nil then
  164. active_material.params[k].value = value
  165. end
  166. controls[k] = { }
  167. local widget = Gtk.FileChooserButton {
  168. title = id,
  169. action = "OPEN",
  170. on_selection_changed = function(chooser)
  171. local abs_filename = chooser:get_filename()
  172. local filename = b2l_relative_path(chooser:get_filename())
  173. if filename and active_material.params[k].value ~= filename then
  174. setting_changed()
  175. end
  176. if filename then
  177. local pbuf,err = GdkPixbuf.Pixbuf.new_from_file(abs_filename)
  178. if pbuf then
  179. active_material.params[k].value = filename
  180. active_material.params[k]._pbuf = pbuf
  181. active_material.params[k]._texture = nil
  182. queue_render()
  183. else
  184. local dialog = Gtk.MessageDialog {
  185. parent = window,
  186. message_type = 'ERROR', buttons = 'CLOSE',
  187. text = ("Failed to open image file: %s"):format(err),
  188. on_response = Gtk.Widget.destroy
  189. }
  190. dialog:show_all()
  191. end
  192. end
  193. end,
  194. }
  195. controls[k].widget = widget
  196. if value ~= nil then
  197. controls[k].widget:set_filename(b2l_absolute_path(value))
  198. end
  199. end
  200. if controls[k].widget then
  201. local grid = settings_grids[tag]
  202. if not grid then
  203. grid = Gtk.Grid {
  204. row_spacing = 5,
  205. margin_start = 30,
  206. }
  207. local expander = Gtk.Expander {
  208. label = tag,
  209. expanded = true,
  210. grid
  211. }
  212. vbox_settings:pack_end(expander, false, false, 5)
  213. settings_grids[tag] = grid
  214. settings_expanders[tag] = expander
  215. grids_pos[tag] = 0
  216. end
  217. controls[k].widget.margin_start = 30
  218. grid:attach(Gtk.Label { label = k, xalign = 0 } , 0, grids_pos[tag], 1, 1)
  219. grid:attach(controls[k].widget, 1, grids_pos[tag], 1, 1)
  220. grids_pos[tag] = grids_pos[tag] + 1
  221. end
  222. end
  223. vbox_settings:show_all()
  224. queue_render()
  225. end
  226. function b2l_dir()
  227. return capi.directory_name(b2l_filename)
  228. end
  229. function b2l_relative_path(file)
  230. return capi.make_path_relative(b2l_dir(), file)
  231. end
  232. function b2l_absolute_path(file)
  233. return b2l_dir() .. file
  234. end
  235. b2l_filter = Gtk.FileFilter {}
  236. b2l_filter:set_name("B2L file")
  237. b2l_filter:add_pattern("*.b2l")
  238. vert_glsl_filter = Gtk.FileFilter {}
  239. vert_glsl_filter:set_name("OpenGL vertex shader")
  240. vert_glsl_filter:add_pattern("*.vert.glsl")
  241. frag_glsl_filter = Gtk.FileFilter {}
  242. frag_glsl_filter:set_name("OpenGL fragment shader")
  243. frag_glsl_filter:add_pattern("*.frag.glsl")
  244. scenes_store = Gtk.ListStore.new {
  245. [1] = GObject.Type.STRING
  246. }
  247. objects_store = Gtk.ListStore.new {
  248. [1] = GObject.Type.STRING
  249. }
  250. materials_store = Gtk.ListStore.new {
  251. [1] = GObject.Type.STRING
  252. }
  253. actions_store = Gtk.ListStore.new {
  254. [1] = GObject.Type.STRING,
  255. [2] = GObject.Type.INT,
  256. [3] = GObject.Type.INT,
  257. }
  258. uv_layer_store = Gtk.ListStore.new {
  259. [1] = GObject.Type.STRING,
  260. }
  261. function load_b2l_file(filename)
  262. local lua_name = filename
  263. if lua_name then
  264. local bin_name = lua_name .. ".bin"
  265. local mat_name = lua_name .. ".mat"
  266. b2l_data = (loadfile(lua_name))()
  267. capi.set_b2l_file(bin_name)
  268. local material_fn = loadfile(mat_name)
  269. if material_fn then
  270. materials = material_fn()
  271. else
  272. materials = {}
  273. end
  274. for k, v in pairs(materials) do
  275. if v.shaders.fs_filename then
  276. local abs_filename = b2l_absolute_path(v.shaders.fs_filename)
  277. local text, err = GLib.file_get_contents(abs_filename)
  278. if text then
  279. v.shaders['_fs_text_org'] = text
  280. v.shaders['_fs_text'] = capi.filter_shader_text(text)
  281. end
  282. end
  283. if v.shaders.vs_filename then
  284. local abs_filename = b2l_absolute_path(v.shaders.vs_filename)
  285. local text, err = GLib.file_get_contents(abs_filename)
  286. if text then
  287. v.shaders['_vs_text_org'] = text
  288. v.shaders['_vs_text'] = capi.filter_shader_text(text)
  289. end
  290. end
  291. end
  292. scenes_store:clear()
  293. for k, v in pairs(b2l_data.scenes) do
  294. scenes_store:append {
  295. [1] = k
  296. }
  297. end
  298. scene_combo:set_active_iter(scenes_store:get_iter_first())
  299. Gtk.main_iteration_do(false)
  300. save_toolbutton.sensitive = false
  301. end
  302. end
  303. b2l_file_button = Gtk.FileChooserButton {
  304. title = "BRT File",
  305. action = "OPEN",
  306. filter = b2l_filter,
  307. hexpand = true,
  308. on_selection_changed = function(chooser)
  309. b2l_filename = chooser:get_filename()
  310. load_b2l_file(b2l_filename)
  311. end,
  312. }
  313. if b2l_filename then
  314. b2l_file_button:set_filename(b2l_filename)
  315. end
  316. fs_edit_button = Gtk.Button {
  317. label = "Edit",
  318. sensitive = false,
  319. on_clicked = function()
  320. GLib.spawn_async(nil,
  321. { editor, fs_chooser:get_filename() },
  322. nil,
  323. GLib.SpawnFlags {
  324. 'SEARCH_PATH'
  325. },
  326. nil)
  327. end,
  328. }
  329. fs_chooser = Gtk.FileChooserButton {
  330. title = "Fragment shader",
  331. action = "OPEN",
  332. filter = frag_glsl_filter,
  333. on_selection_changed = function(chooser)
  334. local abs_filename = chooser:get_filename()
  335. local filename = b2l_relative_path(chooser:get_filename())
  336. if filename and active_material.shaders['fs_filename'] ~= filename then
  337. setting_changed()
  338. end
  339. if filename then
  340. local text, err = GLib.file_get_contents(abs_filename)
  341. if text then
  342. active_material.shaders['fs_filename'] = filename
  343. active_material.shaders['_fs_text_org'] = text
  344. active_material.shaders['_fs_text'] = capi.filter_shader_text(text)
  345. local fs_text = active_material.shaders['_fs_text_org']
  346. local vs_text = active_material.shaders['_vs_text_org']
  347. if fs_text and vs_text then
  348. local uniforms = capi.get_shader_uniforms(vs_text, fs_text)
  349. if uniforms then
  350. update_controls(uniforms)
  351. end
  352. end
  353. end
  354. fs_edit_button.sensitive = true
  355. else
  356. fs_edit_button.sensitive = false
  357. end
  358. end,
  359. }
  360. vs_edit_button = Gtk.Button {
  361. label = "Edit",
  362. sensitive = false,
  363. on_clicked = function()
  364. GLib.spawn_async(nil,
  365. { editor, vs_chooser:get_filename() },
  366. nil,
  367. GLib.SpawnFlags {
  368. 'SEARCH_PATH'
  369. },
  370. nil)
  371. end,
  372. }
  373. vs_chooser = Gtk.FileChooserButton {
  374. title = "Vertex shader",
  375. action = "OPEN",
  376. filter = vert_glsl_filter,
  377. on_selection_changed = function(chooser)
  378. local abs_filename = chooser:get_filename()
  379. local filename = b2l_relative_path(chooser:get_filename())
  380. if filename then
  381. local text, err = GLib.file_get_contents(abs_filename)
  382. if text then
  383. setting_changed()
  384. active_material.shaders['vs_filename'] = filename
  385. active_material.shaders['_vs_text_org'] = text
  386. active_material.shaders['_vs_text'] = capi.filter_shader_text(text)
  387. local fs_text = active_material.shaders['_fs_text_org']
  388. local vs_text = active_material.shaders['_vs_text_org']
  389. if fs_text and vs_text then
  390. local uniforms = capi.get_shader_uniforms(vs_text, fs_text)
  391. if uniforms then
  392. update_controls(uniforms)
  393. end
  394. end
  395. end
  396. vs_edit_button.sensitive = true
  397. else
  398. vs_edit_button.sensitive = false
  399. end
  400. end,
  401. }
  402. function open_b2l_chooser_dialog()
  403. local chooser = Gtk.FileChooserDialog {
  404. title = "Open B2l file",
  405. parent = window,
  406. action = 'OPEN',
  407. on_response = function(self, id, data)
  408. self:hide()
  409. end,
  410. }
  411. chooser:add_button('Cancel', Gtk.ResponseType.CANCEL)
  412. chooser:add_button('Open', Gtk.ResponseType.ACCEPT)
  413. chooser:add_filter(b2l_filter)
  414. local res = chooser:run()
  415. if res == Gtk.ResponseType.ACCEPT then
  416. b2l_file_button:set_filename(chooser:get_filename())
  417. end
  418. end
  419. save_toolbutton = Gtk.ToolButton {
  420. icon_name = "document-save",
  421. sensitive = false,
  422. on_clicked = function(button)
  423. str = "return "
  424. local printer = function(s)
  425. str = str .. s
  426. end
  427. pprint.pformat(materials, {}, printer)
  428. str = str .. "\n"
  429. GLib.file_set_contents(b2l_filename .. ".mat", str)
  430. button.sensitive = false
  431. end,
  432. }
  433. uv_layer_combo = Gtk.ComboBox {
  434. id = "UV layer",
  435. model = uv_layer_store,
  436. active = 0,
  437. cells = {
  438. {
  439. Gtk.CellRendererText(),
  440. { text = 1 }
  441. }
  442. },
  443. on_changed = function(combo)
  444. local active = combo:get_active_iter()
  445. if not active then
  446. return
  447. end
  448. local row = combo.model[active]
  449. if active_material.uv_layer ~= row[1] then
  450. active_material.uv_layer = row[1]
  451. setting_changed()
  452. queue_render()
  453. end
  454. end
  455. }
  456. object_combo = Gtk.ComboBox {
  457. id = "Object",
  458. model = objects_store,
  459. active = 0,
  460. cells = {
  461. {
  462. Gtk.CellRendererText(),
  463. { text = 1 }
  464. }
  465. },
  466. on_changed = function (combo)
  467. local active = combo:get_active_iter()
  468. if not active then
  469. return
  470. end
  471. local row = objects_store[active]
  472. local object_name = row[1]
  473. current_object = object_name
  474. local obj = b2l_data.objects[current_object]
  475. local mesh = b2l_data.meshes[obj.data]
  476. uv_layer_store:clear()
  477. for k, v in pairs(mesh.uv_layers) do
  478. uv_layer_store:append {
  479. [1] = k,
  480. }
  481. end
  482. materials_store:clear()
  483. for i, v in ipairs(mesh.submeshes) do
  484. materials_store:append {
  485. [1] = v.material_name
  486. }
  487. end
  488. material_combo:set_active_iter(materials_store:get_iter_first())
  489. actions_store:clear()
  490. local armature_name = b2l_data.objects[object_name].armature_deform
  491. local scene = b2l_data.scenes[current_scene]
  492. if armature_name then
  493. local armature_obj = b2l_data.objects[armature_name]
  494. if armature_obj.nla_tracks then
  495. for i, track in ipairs(armature_obj.nla_tracks) do
  496. for j, action in ipairs(track) do
  497. local frame_start
  498. local frame_end
  499. frame_start = action.frame_start - scene.frame_start
  500. if frame_start < 0 then
  501. frame_start = 0
  502. end
  503. if action.frame_end > scene.frame_end then
  504. frame_end = scene.frame_end - scene.frame_start
  505. else
  506. frame_end = action.frame_end - scene.frame_start
  507. end
  508. if frame_start <= frame_end then
  509. actions_store:append {
  510. [1] = action.name,
  511. [2] = frame_start,
  512. [3] = frame_end - 1
  513. }
  514. end
  515. end
  516. end
  517. end
  518. end
  519. action_combo:set_active_iter(actions_store:get_iter_first())
  520. Gtk.main_iteration_do(false)
  521. queue_render()
  522. end
  523. }
  524. scene_combo = Gtk.ComboBox {
  525. id = "Scene",
  526. model = scenes_store,
  527. active = 0,
  528. cells = {
  529. {
  530. Gtk.CellRendererText(),
  531. { text = 1 }
  532. }
  533. },
  534. on_changed = function (combo)
  535. local active = combo:get_active_iter()
  536. if not active then
  537. return
  538. end
  539. local row = scenes_store[active]
  540. current_scene = row[1]
  541. objects_store:clear()
  542. objects = {}
  543. for k, v in pairs(b2l_data.scenes[current_scene].objects) do
  544. local object = {}
  545. objects[k] = object
  546. obj_data = b2l_data.objects[k]
  547. if obj_data.nla_tracks then
  548. local actions = {}
  549. object.actions = actions
  550. for i, track in ipairs(obj_data.nla_tracks) do
  551. for j, action in ipairs(track) do
  552. actions[action.name] = action
  553. end
  554. end
  555. end
  556. if obj_data.type == 'MESH' then
  557. objects_store:append {
  558. [1] = k
  559. }
  560. end
  561. end
  562. object_combo:set_active_iter(objects_store:get_iter_first())
  563. Gtk.main_iteration_do(false)
  564. queue_render()
  565. end
  566. }
  567. material_combo = Gtk.ComboBox {
  568. id = "Material",
  569. model = materials_store,
  570. active = 0,
  571. cells = {
  572. {
  573. Gtk.CellRendererText(),
  574. { text = 1 }
  575. }
  576. },
  577. on_changed = function (combo)
  578. local active = combo:get_active_iter()
  579. if not active then
  580. return
  581. end
  582. local row = materials_store[active]
  583. local material_name = row[1]
  584. if materials and material_name then
  585. active_material = materials[material_name]
  586. if not active_material then
  587. active_material = { params = {}, shaders = {}}
  588. materials[material_name] = active_material
  589. end
  590. if active_material.shaders['vs_filename'] then
  591. vs_chooser:set_filename(b2l_absolute_path(active_material.shaders['vs_filename']))
  592. end
  593. if active_material.shaders['fs_filename'] then
  594. fs_chooser:set_filename(b2l_absolute_path(active_material.shaders['fs_filename']))
  595. end
  596. iter = uv_layer_store:get_iter_first()
  597. uv_layer_combo:set_active_iter(iter)
  598. if active_material.uv_layer then
  599. while iter do
  600. local uv_layer_row = uv_layer_store[iter]
  601. if uv_layer_row[1] == active_material.uv_layer then
  602. uv_layer_combo:set_active_iter(iter)
  603. end
  604. iter = uv_layer_store:next(iter)
  605. end
  606. end
  607. Gtk.main_iteration_do(false)
  608. end
  609. end
  610. }
  611. action_combo = Gtk.ComboBox {
  612. id = "Action",
  613. model = actions_store,
  614. active = 0,
  615. cells = {
  616. {
  617. Gtk.CellRendererText(),
  618. { text = 1 }
  619. }
  620. },
  621. on_changed = function (combo)
  622. local active = combo:get_active_iter()
  623. if not active then
  624. return
  625. end
  626. local row = actions_store[active]
  627. local action_name = row[1]
  628. frame_start = row[2]
  629. frame_end = row[3]
  630. frame_delta = 1
  631. action_scale.adjustment.lower = 1
  632. action_scale.adjustment.upper = (frame_end - frame_start) + 1
  633. action_scale:set_value(1)
  634. end
  635. }
  636. action_scale = Gtk.Scale {
  637. adjustment = Gtk.Adjustment {
  638. lower = 1,
  639. upper = 1,
  640. step_increment = 1,
  641. page_increment = 1,
  642. },
  643. digits = 2,
  644. on_value_changed = function(self)
  645. if not playing_animation then
  646. frame_delta = self:get_value() - 1
  647. end
  648. queue_render()
  649. end,
  650. }
  651. function animation_update()
  652. frame_delta = frame_delta + 0.3333;
  653. if (frame_start + frame_delta) >= frame_end then
  654. frame_delta = frame_delta - (frame_end - frame_start)
  655. end
  656. action_scale:set_value(frame_delta + 1)
  657. end
  658. -- Pack everything into the window.
  659. local vbox_main = Gtk.VBox {
  660. {
  661. Gtk.MenuBar {
  662. Gtk.MenuItem {
  663. label = "File",
  664. submenu = Gtk.Menu {
  665. Gtk.MenuItem {
  666. label = "Open",
  667. on_activate = function()
  668. open_b2l_chooser_dialog()
  669. end,
  670. },
  671. Gtk.MenuItem {
  672. label = "Exit",
  673. on_activate = function()
  674. shutdown = true
  675. end,
  676. }
  677. }
  678. },
  679. },
  680. expand = false,
  681. fill = false
  682. },
  683. {
  684. Gtk.Toolbar {
  685. Gtk.ToolButton {
  686. icon_name = "document-open",
  687. on_clicked = function()
  688. open_b2l_chooser_dialog()
  689. end,
  690. },
  691. save_toolbutton,
  692. Gtk.ToolButton {
  693. icon_name = "view-refresh",
  694. on_clicked = function()
  695. local vs_filaname = active_material.shaders['vs_filename']
  696. local fs_filename = active_material.shaders['fs_filename']
  697. if vs_filename then
  698. local abs_filename = b2l_absolute_path(vs_filename)
  699. local text, err = GLib.file_get_contents(abs_filename)
  700. if text then
  701. active_material.shaders['_vs_text_org'] = text
  702. active_material.shaders['_vs_text'] = capi.filter_shader_text(text)
  703. end
  704. end
  705. if fs_filename then
  706. local abs_filename = b2l_absolute_path(fs_filename)
  707. local text, err = GLib.file_get_contents(abs_filename)
  708. if text then
  709. active_material.shaders['_fs_text_org'] = text
  710. active_material.shaders['_fs_text'] = capi.filter_shader_text(text)
  711. end
  712. end
  713. local fs_text = active_material.shaders['_fs_text_org']
  714. local vs_text = active_material.shaders['_vs_text_org']
  715. if fs_text and vs_text then
  716. local uniforms = capi.get_shader_uniforms(vs_text, fs_text)
  717. if uniforms then
  718. update_controls(uniforms)
  719. end
  720. end
  721. end,
  722. },
  723. },
  724. expand = false,
  725. fill = false
  726. },
  727. {
  728. Gtk.Grid {
  729. margin_start = 30,
  730. row_spacing = 3,
  731. column_spacing = 30,
  732. {
  733. Gtk.Label {
  734. label = "B2L File",
  735. xalign = 0,
  736. },
  737. left_attach = 0,
  738. top_attach = 0,
  739. },
  740. {
  741. b2l_file_button,
  742. left_attach = 1,
  743. top_attach = 0,
  744. width = 1,
  745. },
  746. {
  747. Gtk.Label {
  748. label = "Scene",
  749. xalign = 0,
  750. },
  751. left_attach = 0,
  752. top_attach = 1,
  753. },
  754. {
  755. scene_combo,
  756. left_attach = 1,
  757. top_attach = 1,
  758. width = 1,
  759. },
  760. {
  761. Gtk.Label {
  762. label = "Object",
  763. xalign = 0,
  764. },
  765. left_attach = 0,
  766. top_attach = 2,
  767. },
  768. {
  769. object_combo,
  770. left_attach = 1,
  771. top_attach = 2,
  772. width = 1,
  773. },
  774. {
  775. Gtk.Label {
  776. label = "Material",
  777. xalign = 0,
  778. },
  779. left_attach = 0,
  780. top_attach = 3,
  781. },
  782. {
  783. material_combo,
  784. left_attach = 1,
  785. top_attach = 3,
  786. width = 1,
  787. },
  788. {
  789. Gtk.Label {
  790. label = "UV layer",
  791. xalign = 0,
  792. },
  793. left_attach = 0,
  794. top_attach = 4,
  795. },
  796. {
  797. uv_layer_combo,
  798. left_attach = 1,
  799. top_attach = 4,
  800. },
  801. {
  802. Gtk.Label {
  803. label = "Action",
  804. xalign = 0,
  805. },
  806. left_attach = 0,
  807. top_attach = 5,
  808. },
  809. {
  810. action_combo,
  811. left_attach = 1,
  812. top_attach = 5,
  813. width = 1,
  814. },
  815. {
  816. Gtk.Label {
  817. label = "Frame",
  818. xalign = 0,
  819. },
  820. left_attach = 0,
  821. top_attach = 6,
  822. },
  823. {
  824. Gtk.HBox {
  825. {
  826. Gtk.ToolButton {
  827. icon_name = "media-playback-start",
  828. on_clicked = function(button)
  829. if frame_start ~= frame_end then
  830. if button.icon_name == "media-playback-start" then
  831. playing_animation = true
  832. button.icon_name = "media-playback-stop"
  833. else
  834. playing_animation = false
  835. button.icon_name = "media-playback-start"
  836. end
  837. end
  838. queue_render()
  839. end,
  840. },
  841. expand = false
  842. },
  843. action_scale,
  844. },
  845. left_attach = 1,
  846. top_attach = 6,
  847. width = 1,
  848. },
  849. {
  850. Gtk.Label {
  851. label = "Vertex Shader",
  852. xalign = 0,
  853. },
  854. left_attach = 0,
  855. top_attach = 7,
  856. },
  857. {
  858. Gtk.HBox {
  859. vs_chooser,
  860. {
  861. vs_edit_button,
  862. expand = false,
  863. fill = false
  864. },
  865. },
  866. left_attach = 1,
  867. top_attach = 7,
  868. width = 1,
  869. },
  870. {
  871. Gtk.Label {
  872. label = "Fragment Shader",
  873. xalign = 0,
  874. },
  875. left_attach = 0,
  876. top_attach = 8,
  877. },
  878. {
  879. Gtk.HBox {
  880. fs_chooser,
  881. {
  882. fs_edit_button,
  883. expand = false,
  884. fill = false
  885. },
  886. },
  887. left_attach = 1,
  888. top_attach = 8,
  889. width = 1,
  890. },
  891. },
  892. expand = false,
  893. fill = false
  894. },
  895. {
  896. vbox_settings,
  897. expand = false,
  898. fill = true
  899. },
  900. }
  901. window_main:add(vbox_main)
  902. -- Show windows and start the loop.
  903. window_main:show_all()